From 78f1d962cd3d62331abdac329f113e34403b930b Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:01:31 +0900 Subject: [PATCH] feat(YouTube): Add `Snack bar components` patch --- .../shared/patches/spans/Filter.java | 2 +- .../youtube/patches/general/GeneralPatch.java | 4 - .../patches/general/SnackBarPatch.java | 115 ++++++ .../youtube/patches/spans/SnackBarFilter.java | 62 ++++ .../extension/youtube/settings/Settings.java | 8 +- .../shared/drawable/DrawableColorHookPatch.kt | 12 +- .../general/components/Fingerprints.kt | 10 - .../components/LayoutComponentsPatch.kt | 17 - .../youtube/general/snackbar/Fingerprints.kt | 68 ++++ .../snackbar/SnackBarComponentsPatch.kt | 340 ++++++++++++++++++ .../patches/youtube/utils/patch/PatchList.kt | 4 + .../utils/resourceid/SharedResourceIdPatch.kt | 6 + .../youtube/settings/host/values/strings.xml | 25 +- .../youtube/settings/xml/revanced_prefs.xml | 13 +- 14 files changed, 642 insertions(+), 44 deletions(-) create mode 100644 extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SnackBarPatch.java create mode 100644 extensions/shared/src/main/java/app/revanced/extension/youtube/patches/spans/SnackBarFilter.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/Filter.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/Filter.java index 566fb96e0..4b81cc66b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/Filter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/Filter.java @@ -61,7 +61,7 @@ public abstract class Filter { */ public boolean skip(String conversionContext, SpannableString spannableString, Object span, int start, int end, int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) { - if (BaseSettings.ENABLE_DEBUG_LOGGING.get()) { + if (BaseSettings.ENABLE_DEBUG_BUFFER_LOGGING.get()) { String filterSimpleName = getClass().getSimpleName(); Logger.printDebug(() -> filterSimpleName + " Removed setSpan: " + spanType.type); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java index 713b93ed0..db497199e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java @@ -188,10 +188,6 @@ public class GeneralPatch { return Settings.HIDE_FLOATING_MICROPHONE.get() || original; } - public static boolean hideSnackBar() { - return Settings.HIDE_SNACK_BAR.get(); - } - // endregion // region [Hide navigation bar components] patch diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SnackBarPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SnackBarPatch.java new file mode 100644 index 000000000..07c20f03b --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SnackBarPatch.java @@ -0,0 +1,115 @@ +package app.revanced.extension.youtube.patches.general; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.widget.FrameLayout; + +import java.util.concurrent.atomic.AtomicBoolean; + +import app.revanced.extension.shared.utils.ResourceUtils; +import app.revanced.extension.shared.utils.Utils; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.utils.ThemeUtils; + +@SuppressWarnings("unused") +public final class SnackBarPatch { + private static final boolean HIDE_SNACK_BAR = + Settings.HIDE_SNACK_BAR.get(); + private static final boolean HIDE_SERVER_SIDE_SNACK_BAR = + Settings.HIDE_SERVER_SIDE_SNACK_BAR.get(); + private static final boolean CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND = + !HIDE_SNACK_BAR && !HIDE_SERVER_SIDE_SNACK_BAR && Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND.get(); + private static final boolean INVERT_SNACK_BAR_THEME = + !HIDE_SNACK_BAR && Settings.INVERT_SNACK_BAR_THEME.get(); + private static final boolean INVERT_SERVER_SIDE_SNACK_BAR_THEME = + !HIDE_SERVER_SIDE_SNACK_BAR && INVERT_SNACK_BAR_THEME; + private static final int SNACK_BAR_BLACK_COLOR = 0xFF0F0F0F; + private static final int SNACK_BAR_WHITE_COLOR = 0xFFF1F1F1; + private static final AtomicBoolean lithoSnackBarLoaded = new AtomicBoolean(false); + private static int blackColor = 0; + private static int whiteColor = 0; + + public static boolean hideSnackBar() { + return HIDE_SNACK_BAR; + } + + public static void hideLithoSnackBar(FrameLayout frameLayout) { + if (HIDE_SERVER_SIDE_SNACK_BAR) { + Utils.hideViewByLayoutParams(frameLayout); + } + } + + public static void setLithoSnackBarBackground(View view) { + if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND) { + int snackBarRoundedCornersBackgroundIdentifier = + ResourceUtils.getDrawableIdentifier("snackbar_rounded_corners_background"); + Context mContext = invertSnackBarTheme(view.getContext()); + Drawable snackBarRoundedCornersBackground = mContext.getDrawable(snackBarRoundedCornersBackgroundIdentifier); + if (snackBarRoundedCornersBackground != null) { + view.setBackground(snackBarRoundedCornersBackground); + } + } + } + + public static void setLithoSnackBarBackgroundColor(FrameLayout frameLayout, int color) { + if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND) { + return; + } + frameLayout.setBackgroundColor(color); + } + + public static Context invertSnackBarTheme(Context mContext) { + if (INVERT_SERVER_SIDE_SNACK_BAR_THEME) { + String styleId = ThemeUtils.isDarkTheme() + ? "Base.Theme.YouTube.Light" + : "Base.Theme.YouTube.Dark"; + int styleIdentifier = ResourceUtils.getStyleIdentifier(styleId); + mContext = new ContextThemeWrapper(mContext, styleIdentifier); + } + + return mContext; + } + + public static Enum invertSnackBarTheme(Enum appTheme, Enum darkTheme) { + if (INVERT_SNACK_BAR_THEME) { + return appTheme == darkTheme + ? null + : darkTheme; + } + + return appTheme; + } + + public static void lithoSnackBarLoaded() { + lithoSnackBarLoaded.compareAndSet(false, true); + } + + public static int getLithoColor(int originalValue) { + if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND && + lithoSnackBarLoaded.compareAndSet(true, false)) { + if (originalValue == SNACK_BAR_BLACK_COLOR) { + return INVERT_SERVER_SIDE_SNACK_BAR_THEME + ? getWhiteColor() + : getBlackColor(); + } else if (originalValue == SNACK_BAR_WHITE_COLOR) { + return INVERT_SERVER_SIDE_SNACK_BAR_THEME + ? getBlackColor() + : getWhiteColor(); + } + } + + return originalValue; + } + + private static int getBlackColor() { + if (blackColor == 0) blackColor = ResourceUtils.getColor("revanced_snack_bar_color_dark"); + return blackColor; + } + + private static int getWhiteColor() { + if (whiteColor == 0) whiteColor = ResourceUtils.getColor("revanced_snack_bar_color_light"); + return whiteColor; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/spans/SnackBarFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/spans/SnackBarFilter.java new file mode 100644 index 000000000..4dbed0834 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/spans/SnackBarFilter.java @@ -0,0 +1,62 @@ +package app.revanced.extension.youtube.patches.spans; + +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; + +import app.revanced.extension.shared.patches.spans.Filter; +import app.revanced.extension.shared.patches.spans.SpanType; +import app.revanced.extension.shared.patches.spans.StringFilterGroup; +import app.revanced.extension.shared.utils.ResourceUtils; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.utils.ThemeUtils; + +@SuppressWarnings({"unused", "FieldCanBeLocal"}) +public final class SnackBarFilter extends Filter { + private static final boolean HIDE_SNACK_BAR = + Settings.HIDE_SNACK_BAR.get() || Settings.HIDE_SERVER_SIDE_SNACK_BAR.get(); + private static final boolean CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND = + !HIDE_SNACK_BAR && Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND.get(); + private static final boolean INVERT_SNACK_BAR_THEME = + !HIDE_SNACK_BAR && Settings.INVERT_SNACK_BAR_THEME.get(); + + private final ForegroundColorSpan foregroundColorSpanBlack = + new ForegroundColorSpan(ResourceUtils.getColor("yt_black1")); + private final ForegroundColorSpan foregroundColorSpanWhite = + new ForegroundColorSpan(ResourceUtils.getColor("yt_white1")); + + public SnackBarFilter() { + addCallbacks( + new StringFilterGroup( + Settings.CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND, + "snackbar.eml|" + ) + ); + } + + private ForegroundColorSpan getForegroundColorSpan() { + if (INVERT_SNACK_BAR_THEME) { + return ThemeUtils.isDarkTheme() + ? foregroundColorSpanWhite + : foregroundColorSpanBlack; + } + return ThemeUtils.isDarkTheme() + ? foregroundColorSpanBlack + : foregroundColorSpanWhite; + } + + @Override + public boolean skip(String conversionContext, SpannableString spannableString, Object span, + int start, int end, int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) { + if (CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND && spanType == SpanType.FOREGROUND_COLOR) { + spannableString.setSpan( + getForegroundColorSpan(), + start, + end, + flags + ); + return super.skip(conversionContext, spannableString, span, start, end, flags, isWord, spanType, matchedGroup); + } + + return false; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index ffb880a56..df4e27a13 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -93,7 +93,6 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_FEED_SURVEY = new BooleanSetting("revanced_hide_feed_survey", TRUE); public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", TRUE); - // PreferenceScreen: Feed - Category bar public static final BooleanSetting HIDE_CATEGORY_BAR_IN_FEED = new BooleanSetting("revanced_hide_category_bar_in_feed", FALSE, true); public static final BooleanSetting HIDE_CATEGORY_BAR_IN_SEARCH = new BooleanSetting("revanced_hide_category_bar_in_search", FALSE, true); @@ -152,7 +151,6 @@ public class Settings extends BaseSettings { public static final BooleanSetting ENABLE_GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_enable_gradient_loading_screen", FALSE, true); public static final BooleanSetting HIDE_FLOATING_MICROPHONE = new BooleanSetting("revanced_hide_floating_microphone", TRUE, true); public static final BooleanSetting HIDE_GRAY_SEPARATOR = new BooleanSetting("revanced_hide_gray_separator", TRUE); - public static final BooleanSetting HIDE_SNACK_BAR = new BooleanSetting("revanced_hide_snack_bar", FALSE); public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE); public static final EnumSetting CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true); @@ -232,6 +230,12 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SETTINGS_MENU_POST_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_post_purchase", FALSE, true); public static final BooleanSetting HIDE_SETTINGS_MENU_THIRD_PARTY = new BooleanSetting("revanced_hide_settings_menu_third_party", FALSE, true); + // PreferenceScreen: General - Snack bar + public static final BooleanSetting HIDE_SNACK_BAR = new BooleanSetting("revanced_hide_snack_bar", FALSE, true); + public static final BooleanSetting HIDE_SERVER_SIDE_SNACK_BAR = new BooleanSetting("revanced_hide_server_side_snack_bar", FALSE, true); + public static final BooleanSetting CHANGE_SERVER_SIDE_SNACK_BAR_BACKGROUND = new BooleanSetting("revanced_change_server_side_snack_bar_background", FALSE, true, "revanced_change_server_side_snack_bar_background_user_dialog_message"); + public static final BooleanSetting INVERT_SNACK_BAR_THEME = new BooleanSetting("revanced_invert_snack_bar_theme", FALSE, true); + // PreferenceScreen: General - Toolbar public static final BooleanSetting CHANGE_YOUTUBE_HEADER = new BooleanSetting("revanced_change_youtube_header", TRUE, true); public static final BooleanSetting ENABLE_WIDE_SEARCH_BAR = new BooleanSetting("revanced_enable_wide_search_bar", FALSE, true); diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorHookPatch.kt index 89d8c816a..6afc0f311 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/drawable/DrawableColorHookPatch.kt @@ -30,13 +30,15 @@ val drawableColorHookPatch = bytecodePatch( } internal fun addDrawableColorHook( - methodDescriptor: String + methodDescriptor: String, + highPriority: Boolean = false ) { insertMethod.addInstructions( - insertIndex + offset, """ - invoke-static {v$insertRegister}, $methodDescriptor - move-result v$insertRegister - """ + if (highPriority) insertIndex else insertIndex + offset, + """ + invoke-static {v$insertRegister}, $methodDescriptor + move-result v$insertRegister + """ ) offset += 2 } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt index 76355a112..e9e4a2e29 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt @@ -69,16 +69,6 @@ internal val appBlockingCheckResultToStringFingerprint = legacyFingerprint( strings = listOf("AppBlockingCheckResult{intent=") ) -internal val bottomUiContainerFingerprint = legacyFingerprint( - name = "bottomUiContainerFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("L", "L"), - customFingerprint = { method, _ -> - method.definingClass.endsWith("/BottomUiContainer;") - } -) - internal val floatingMicrophoneFingerprint = legacyFingerprint( name = "floatingMicrophoneFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt index e5a06857b..7782e7741 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt @@ -2,12 +2,10 @@ package app.revanced.patches.youtube.general.components import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.shared.settingmenu.settingsMenuPatch @@ -215,21 +213,6 @@ val layoutComponentsPatch = bytecodePatch( // endregion - // region patch for hide snack bar - - bottomUiContainerFingerprint.methodOrThrow().apply { - addInstructionsWithLabels( - 0, """ - invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->hideSnackBar()Z - move-result v0 - if-eqz v0, :show - return-void - """, ExternalLabel("show", getInstruction(0)) - ) - } - - // endregion - // region patch for hide tooltip content tooltipContentFullscreenFingerprint.methodOrThrow().apply { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/Fingerprints.kt new file mode 100644 index 000000000..dfa3a62d7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/Fingerprints.kt @@ -0,0 +1,68 @@ +package app.revanced.patches.youtube.general.snackbar + +import app.revanced.patches.youtube.utils.resourceid.insetElementsWrapper +import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +private const val BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR = + "Lcom/google/android/apps/youtube/app/common/ui/bottomui/BottomUiContainer;" + +internal val bottomUiContainerFingerprint = legacyFingerprint( + name = "bottomUiContainerFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "L"), + customFingerprint = { _, classDef -> + classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR + } +) + +internal val bottomUiContainerPreFingerprint = legacyFingerprint( + name = "bottomUiContainerPreFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "L", "L"), + opcodes = listOf( + Opcode.IF_NEZ, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ), + customFingerprint = { _, classDef -> + classDef.type == BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR + } +) + +internal val bottomUiContainerThemeFingerprint = legacyFingerprint( + name = "bottomUiContainerThemeFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf(BOTTOM_UI_CONTAINER_CLASS_DESCRIPTOR), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.SGET_OBJECT, + Opcode.IF_NE, + Opcode.CONST, + ), +) + +internal val lithoSnackBarFingerprint = legacyFingerprint( + name = "lithoSnackBarFingerprint", + returnType = "Landroid/view/View;", + literals = listOf(insetElementsWrapper), + customFingerprint = { method, _ -> + indexOfBackGroundColor(method) >= 0 + } +) + +internal fun indexOfBackGroundColor(method: Method) = + method.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setBackgroundColor" + } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt new file mode 100644 index 000000000..032566712 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt @@ -0,0 +1,340 @@ +package app.revanced.patches.youtube.general.snackbar + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.drawable.addDrawableColorHook +import app.revanced.patches.shared.drawable.drawableColorHookPatch +import app.revanced.patches.shared.spans.addSpanFilter +import app.revanced.patches.shared.spans.inclusiveSpanPatch +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH +import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH +import app.revanced.patches.youtube.utils.patch.PatchList.SNACK_BAR_COMPONENTS +import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference +import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.findElementByAttributeValueOrThrow +import app.revanced.util.findMethodOrThrow +import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getNode +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.valueOrThrow +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 +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import org.w3c.dom.Element + +private const val EXTENSION_CLASS_DESCRIPTOR = + "$GENERAL_PATH/SnackBarPatch;" +private const val FILTER_CLASS_DESCRIPTOR = + "$SPANS_PATH/SnackBarFilter;" + +private val snackBarComponentsBytecodePatch = bytecodePatch( + description = "snackBarComponentsBytecodePatch" +) { + dependsOn( + settingsPatch, + sharedResourceIdPatch, + drawableColorHookPatch, + inclusiveSpanPatch, + ) + + execute { + bottomUiContainerFingerprint.methodOrThrow().apply { + addInstructionsWithLabels( + 0, """ + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideSnackBar()Z + move-result v0 + if-eqz v0, :show + return-void + """, ExternalLabel("show", getInstruction(0)) + ) + } + + bottomUiContainerPreFingerprint.matchOrThrow().let { + it.method.apply { + val insertIndex = it.patternMatch!!.startIndex + 1 + + addInstruction( + insertIndex, + "invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->lithoSnackBarLoaded()V" + ) + } + } + + bottomUiContainerThemeFingerprint.matchOrThrow().let { + it.method.apply { + val startIndex = it.patternMatch!!.startIndex + val appThemeIndex = startIndex + 1 + val darkThemeIndex = startIndex + 2 + val insertIndex = startIndex + 3 + + val appThemeRegister = + getInstruction(appThemeIndex).registerA + val darkThemeRegister = + getInstruction(darkThemeIndex).registerA + + addInstructions( + insertIndex, """ + invoke-static {v$appThemeRegister, v$darkThemeRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$appThemeRegister + """ + ) + } + } + + fun MutableMethod.setBackground(index: Int, register: Int) = + addInstruction( + index, + "invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->setLithoSnackBarBackground(Landroid/view/View;)V" + ) + + lithoSnackBarFingerprint.methodOrThrow().apply { + val backGroundColorIndex = indexOfBackGroundColor(this) + val viewRegister = + getInstruction(backGroundColorIndex).registerC + val colorRegister = + getInstruction(backGroundColorIndex).registerD + + replaceInstruction( + backGroundColorIndex, + "invoke-static {v$viewRegister, v$colorRegister}, $EXTENSION_CLASS_DESCRIPTOR->" + + "setLithoSnackBarBackgroundColor(Landroid/widget/FrameLayout;I)V" + ) + setBackground(backGroundColorIndex + 2, viewRegister) + + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + instruction.opcode == Opcode.CHECK_CAST && + (instruction as? ReferenceInstruction)?.reference?.toString() == "Landroid/widget/FrameLayout;" + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = + getInstruction(index).registerA + + setBackground(index + 1, register) + } + + findMethodOrThrow(definingClass).apply { + val contextIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Landroid/content/Context;" + } + val contextRegister = + getInstruction(contextIndex).registerA + + addInstructions( + contextIndex, """ + invoke-static {v$contextRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Landroid/content/Context;)Landroid/content/Context; + move-result-object v$contextRegister + """ + ) + + val viewIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Landroid/widget/FrameLayout;" + } + val viewRegister = + getInstruction(viewIndex).registerA + + addInstructions( + viewIndex, + "invoke-static {v$viewRegister}, $EXTENSION_CLASS_DESCRIPTOR->hideLithoSnackBar(Landroid/widget/FrameLayout;)V" + ) + } + } + + addDrawableColorHook("$EXTENSION_CLASS_DESCRIPTOR->getLithoColor(I)I", true) + addSpanFilter(FILTER_CLASS_DESCRIPTOR) + } +} + +@Suppress("unused") +val snackBarComponentsPatch = resourcePatch( + SNACK_BAR_COMPONENTS.title, + SNACK_BAR_COMPONENTS.summary, +) { + compatibleWith(COMPATIBLE_PACKAGE) + + dependsOn( + settingsPatch, + snackBarComponentsBytecodePatch, + ) + + val catppuccinMochaColor = "#FF181825" + val catppuccinLatteColor = "#FFE6E9EF" + + val availableDarkTheme = mapOf( + "Amoled Black" to "@android:color/black", + "Catppuccin (Mocha)" to "#FF181825", + "Dark Pink" to "#FF290025", + "Dark Blue" to "#FF001029", + "Dark Green" to "#FF002905", + "Dark Yellow" to "#FF282900", + "Dark Orange" to "#FF291800", + "Dark Red" to "#FF290000", + ) + + val availableLightTheme = mapOf( + "White" to "@android:color/white", + "Catppuccin (Latte)" to "#FFE6E9EF", + "Light Pink" to "#FFFCCFF3", + "Light Blue" to "#FFD1E0FF", + "Light Green" to "#FFCCFFCC", + "Light Yellow" to "#FFFDFFCC", + "Light Orange" to "#FFFFE6CC", + "Light Red" to "#FFFFD6D6", + ) + + val cornerRadiusOption = stringOption( + key = "cornerRadius", + default = "8.0dip", + title = "Corner radius", + description = "Specify a corner radius for the snack bar.", + required = true, + ) + + val darkThemeBackgroundColor = stringOption( + key = "darkThemeBackgroundColor", + default = catppuccinMochaColor, + values = availableDarkTheme, + title = "Dark theme background color", + description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.", + required = true, + ) + + val lightThemeBackgroundColor = stringOption( + key = "lightThemeBackgroundColor", + default = catppuccinLatteColor, + values = availableLightTheme, + title = "Light theme background color", + description = "Specify a background color for the snack bar. You can specify hex color (#AARRGGBB) or color resource reference.", + required = true, + ) + + val strokeColorOption = stringOption( + key = "strokeColor", + default = "", + values = mapOf( + "None" to "", + "Blue" to "?attr/ytThemedBlue", + "Chip" to "?attr/ytChipBackground" + ), + title = "Stroke color", + description = "Specify a stroke color for the snack bar. You can specify hex color.", + required = true, + ) + + execute { + + // Check patch options first. + val cornerRadius = cornerRadiusOption + .valueOrThrow() + val darkThemeColor = darkThemeBackgroundColor + .valueOrThrow() + val lightThemeColor = lightThemeBackgroundColor + .valueOrThrow() + val strokeColor = strokeColorOption + .valueOrThrow() + + val snackBarColorAttr = "snackBarColor" + val snackBarColorAttrReference = "?attr/$snackBarColorAttr" + val snackBarColorDark = "revanced_snack_bar_color_dark" + val snackBarColorDarkReference = "@color/$snackBarColorDark" + val snackBarColorLight = "revanced_snack_bar_color_light" + val snackBarColorLightReference = "@color/$snackBarColorLight" + + document("res/values/colors.xml").use { document -> + mapOf( + snackBarColorDark to darkThemeColor, + snackBarColorLight to lightThemeColor, + ).forEach { (k, v) -> + val colorElement = document.createElement("color") + + colorElement.setAttribute("name", k) + colorElement.textContent = v + + document.getElementsByTagName("resources").item(0) + .appendChild(colorElement) + } + } + + document("res/values/attrs.xml").use { document -> + (document.getElementsByTagName("resources").item(0) as Element).appendChild( + document.createElement("attr").apply { + setAttribute("format", "reference|color") + setAttribute("name", snackBarColorAttr) + } + ) + } + + document("res/values/styles.xml").use { document -> + mapOf( + "Base.Theme.YouTube.Dark" to snackBarColorLightReference, + "Base.Theme.YouTube.Light" to snackBarColorDarkReference, + ).forEach { (styleName, colorName) -> + val snackBarColorNode = document.createElement("item") + snackBarColorNode.setAttribute("name", snackBarColorAttr) + snackBarColorNode.appendChild(document.createTextNode(colorName)) + + document.childNodes.findElementByAttributeValueOrThrow( + "name", + styleName, + ).appendChild(snackBarColorNode) + } + } + + document("res/drawable/snackbar_rounded_corners_background.xml").use { document -> + document.getNode("corners").apply { + arrayOf( + "android:bottomLeftRadius", + "android:bottomRightRadius", + "android:topLeftRadius", + "android:topRightRadius", + ).forEach { + attributes.getNamedItem(it).nodeValue = cornerRadius + } + } + document.getNode("solid").apply { + attributes.getNamedItem("android:color").nodeValue = snackBarColorAttrReference + } + if (!strokeColor.isEmpty()) { + (document.getElementsByTagName("shape").item(0) as Element).appendChild( + document.createElement("stroke").apply { + setAttribute("android:width", "1.0dip") + setAttribute("android:color", strokeColor) + } + ) + } + } + + // region add settings + + addPreference( + arrayOf( + "PREFERENCE_SCREEN: GENERAL", + "SETTINGS: SNACK_BAR_COMPONENTS" + ), + SNACK_BAR_COMPONENTS + ) + + // endregion + + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt index 295d97ecd..41a56de93 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt @@ -217,6 +217,10 @@ internal enum class PatchList( "Shorts components", "Adds options to hide or change components related to YouTube Shorts." ), + SNACK_BAR_COMPONENTS( + "Snack bar components", + "Adds options to hide or change components related to the snack bar." + ), SPONSORBLOCK( "SponsorBlock", "Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content." diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index f12b03dea..bf1b58fe0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -121,6 +121,8 @@ var insetOverlayViewLayout = -1L private set var interstitialsContainer = -1L private set +var insetElementsWrapper = -1L + private set var menuItemView = -1L private set var metaPanel = -1L @@ -461,6 +463,10 @@ internal val sharedResourceIdPatch = resourcePatch( ID, "interstitials_container" ] + insetElementsWrapper = resourceMappings[ + LAYOUT, + "inset_elements_wrapper" + ] menuItemView = resourceMappings[ ID, "menu_item_view" diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index a2776613a..ae042b980 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -425,9 +425,6 @@ Limitation: Back button on the toolbar may not work." Hide gray separators Gray separators are hidden. Gray separators are shown. - Hide snack bar - Snack bar is hidden. - Snack bar is shown. Remove viewer discretion dialog "Removes the viewer discretion dialog. This does not bypass the age restriction. It just accepts it automatically." @@ -677,6 +674,28 @@ If this setting do not take effect, try switching to Incognito mode." About menu is hidden. About menu is shown. + + Snack bar + Hide or change components related to snack bar. + + Hide snack bar + Snack bar is hidden. + Snack bar is shown. + Hide server-side snack bar + Server-side snack bar is hidden. + Server-side snack bar is shown. + Invert snack bar theme + Theme of the snack bar is inverted. + Theme of the snack bar is not inverted. + Change server-side snack bar background + Background color of the server-side snackbar has changed. + Background color of the server-side snackbar has not changed. + "Some snack bars use a theme defined on the server side, not the app theme. + +Change the background color of these snack bars. + +If there are server-side changes, the background color of the snack bar may not change." + Toolbar Hide or change components located on the toolbar, such as the search bar, toolbar buttons, and header. diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index 3bc584cab..ddf290997 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -244,6 +244,15 @@ SETTINGS: HIDE_LAYOUT_COMPONENTS --> + + + SETTINGS: HIDE_LAYOUT_COMPONENTS --> @@ -914,6 +922,7 @@ +