From 8f2af9f869b5bd7deca77132af1ed315821aabe2 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Wed, 25 Dec 2024 14:09:56 +0400 Subject: [PATCH 1/8] feat(YouTube): Add `Change form factor` patch --- .../patches/AlternativeThumbnailsPatch.java | 15 ++-- .../patches/ChangeFormFactorPatch.java | 53 +++++++++++++ .../youtube/patches/TabletLayoutPatch.java | 16 ---- .../components/KeywordContentFilter.java | 15 ++-- .../patches/components/ShortsFilter.java | 2 +- .../extension/youtube/settings/Settings.java | 4 +- .../youtube/shared/NavigationBar.java | 4 + patches/api/patches.api | 5 +- .../formfactor/ChangeFormFactorPatch.kt | 75 +++++++++++++++++++ .../youtube/layout/formfactor/Fingerprints.kt | 48 ++++++++++++ .../layout/tablet/EnableTabletLayoutPatch.kt | 67 ++--------------- .../youtube/layout/tablet/Fingerprints.kt | 25 ------- .../resources/addresources/values/arrays.xml | 25 +++++-- .../resources/addresources/values/strings.xml | 12 +-- 14 files changed, 231 insertions(+), 135 deletions(-) create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java delete mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/TabletLayoutPatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt delete mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/Fingerprints.kt diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AlternativeThumbnailsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AlternativeThumbnailsPatch.java index 92be08433e..c670a79de6 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AlternativeThumbnailsPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AlternativeThumbnailsPatch.java @@ -176,14 +176,13 @@ private static ThumbnailOption optionSettingForCurrentNavigation() { // Unknown tab, treat as the home tab; return homeOption; } - if (selectedNavButton == NavigationButton.HOME) { - return homeOption; - } - if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) { - return subscriptionsOption; - } - // A library tab variant is active. - return libraryOption; + + return switch (selectedNavButton) { + case SUBSCRIPTIONS, NOTIFICATIONS -> subscriptionsOption; + case LIBRARY -> libraryOption; + // Home or explore tab. + default -> homeOption; + }; } /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java new file mode 100644 index 0000000000..de083203a2 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java @@ -0,0 +1,53 @@ +package app.revanced.extension.youtube.patches; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class ChangeFormFactorPatch { + + public enum FormFactor { + /** + * Unmodified, and same as un-patched. + */ + DEFAULT(null), + /** + * Some changes include: + * - Explore tab is present + * - watch history is missing + * - feed thumbnails fade in. + */ + UNKNOWN(0), + SMALL(1), + LARGE(2), + /** + * Cars with 'Google built-in'. + * Layout seems identical to {@link #UNKNOWN} + * even when using an Android Automotive device. + */ + AUTOMOTIVE(3), + WEARABLE(4); + + @Nullable + final Integer formFactorType; + + FormFactor(@Nullable Integer formFactorType) { + this.formFactorType = formFactorType; + } + } + + @Nullable + private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType; + + /** + * Injection point. + */ + public static int getFormFactor(int original) { + return FORM_FACTOR_TYPE == null + ? original + : FORM_FACTOR_TYPE; + } + +} \ No newline at end of file diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/TabletLayoutPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/TabletLayoutPatch.java deleted file mode 100644 index f2ae035980..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/TabletLayoutPatch.java +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.extension.youtube.patches; - -import app.revanced.extension.youtube.settings.Settings; - -@SuppressWarnings("unused") -public final class TabletLayoutPatch { - - private static final boolean TABLET_LAYOUT_ENABLED = Settings.TABLET_LAYOUT.get(); - - /** - * Injection point. - */ - public static boolean getTabletLayoutEnabled() { - return TABLET_LAYOUT_ENABLED; - } -} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java index b451fd282f..ff55d18a9f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java @@ -528,14 +528,13 @@ private boolean hideKeywordSettingIsActive() { if (selectedNavButton == null) { return hideHome; // Unknown tab, treat the same as home. } - if (selectedNavButton == NavigationButton.HOME) { - return hideHome; - } - if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) { - return hideSubscriptions; - } - // User is in the Library or Notifications tab. - return false; + + return switch (selectedNavButton) { + case HOME, EXPLORE -> hideHome; + case SUBSCRIPTIONS -> hideSubscriptions; + // User is in the Library or notifications. + default -> false; + }; } private void updateStats(boolean videoWasHidden, @Nullable String keyword) { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java index ac8218c417..3a57fb8361 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java @@ -366,7 +366,7 @@ private static boolean shouldHideShortsFeedItems() { } return switch (selectedNavButton) { - case HOME -> hideHome; + case HOME, EXPLORE -> hideHome; case SUBSCRIPTIONS -> hideSubscriptions; case LIBRARY -> hideHistory; default -> false; 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 765f03f9ad..389c873770 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 @@ -7,8 +7,8 @@ import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew; import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parentsAny; +import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor; import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage; -import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType; @@ -200,12 +200,12 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE); // General layout + public static final EnumSetting CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true); public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true); public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true); public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE, "revanced_remove_viewer_discretion_dialog_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message"); - public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message"); public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true); public static final EnumSetting CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true); public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION)); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java index 3a24567956..82dd83d958 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java @@ -250,6 +250,10 @@ public enum NavigationButton { * This tab will never be in a selected state, even if the create video UI is on screen. */ CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"), + /** + * Only shown to automotive layout. + */ + EXPLORE("TAB_EXPLORE"), SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"), /** * Notifications tab. Only present when diff --git a/patches/api/patches.api b/patches/api/patches.api index b76994c3a6..31c29b7ebf 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1106,6 +1106,10 @@ public final class app/revanced/patches/youtube/layout/buttons/overlay/HidePlaye public static final fun getHidePlayerOverlayButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatchKt { + public static final fun getChangeFormFactorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/layout/hide/endscreencards/HideEndscreenCardsPatchKt { public static final fun getHideEndscreenCardsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1235,7 +1239,6 @@ public final class app/revanced/patches/youtube/layout/startupshortsreset/Disabl } public final class app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatchKt { - public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String; public static final fun getEnableTabletLayoutPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt new file mode 100644 index 0000000000..ee32212982 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt @@ -0,0 +1,75 @@ +package app.revanced.patches.youtube.layout.formfactor + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;" + +@Suppress("unused") +val changeFormFactorPatch = bytecodePatch( + name = "Change form factor", + description = "Adds an option to change the UI appearance to a tablet or phone.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + + compatibleWith( + "com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + "19.47.53", + ), + ) + + execute { + addResources("youtube", "layout.formfactor.changeFormFactorPatch") + + PreferenceScreen.GENERAL_LAYOUT.addPreferences( + ListPreference( + "revanced_change_form_factor", + summaryKey = null, + ) + ) + + createPlayerRequestBodyWithModelFingerprint.method.apply { + val formFactorEnumClass = formFactorEnumConstructorFingerprint.originalClassDef.type + + val index = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.IGET && + reference?.definingClass == formFactorEnumClass && + reference.type == "I" + } + val register = getInstruction(index).registerA + + addInstructions( + index + 1, + """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getFormFactor(I)I + move-result v$register + """ + ) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt new file mode 100644 index 0000000000..230b7933e0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.youtube.layout.formfactor + +import app.revanced.patcher.fingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +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.FieldReference + +internal val formFactorEnumConstructorFingerprint = fingerprint { + accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) + strings( + "UNKNOWN_FORM_FACTOR", + "SMALL_FORM_FACTOR", + "LARGE_FORM_FACTOR" + ) +} + +internal val createPlayerRequestBodyWithModelFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("L") + parameters() + opcodes(Opcode.OR_INT_LIT16) + custom { method, _ -> + method.indexOfModelInstruction() >= 0 && + method.indexOfReleaseInstruction() >= 0 + } +} + +private fun Method.indexOfModelInstruction() = + indexOfFirstInstruction { + val reference = getReference() + + reference?.definingClass == "Landroid/os/Build;" && + reference.name == "MODEL" && + reference.type == "Ljava/lang/String;" + } + +internal fun Method.indexOfReleaseInstruction(): Int = + indexOfFirstInstruction { + val reference = getReference() + + reference?.definingClass == "Landroid/os/Build${'$'}VERSION;" && + reference.name == "RELEASE" && + reference.type == "Ljava/lang/String;" + } + diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt index ac460dada5..c04f5e99c7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/EnableTabletLayoutPatch.kt @@ -1,66 +1,9 @@ package app.revanced.patches.youtube.layout.tablet -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.util.smali.ExternalLabel -import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.settings.PreferenceScreen -import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.patches.youtube.layout.formfactor.changeFormFactorPatch -const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/TabletLayoutPatch;" - -val enableTabletLayoutPatch = bytecodePatch( - name = "Enable tablet layout", - description = "Adds an option to enable tablet layout.", -) { - dependsOn( - sharedExtensionPatch, - settingsPatch, - addResourcesPatch, - ) - - compatibleWith( - "com.google.android.youtube"( - "18.38.44", - "18.49.37", - "19.16.39", - "19.25.37", - "19.34.42", - "19.43.41", - "19.45.38", - "19.46.42", - "19.47.53", - ), - ) - - execute { - addResources("youtube", "layout.tablet.enableTabletLayoutPatch") - - PreferenceScreen.GENERAL_LAYOUT.addPreferences( - SwitchPreference("revanced_tablet_layout"), - ) - - getFormFactorFingerprint.method.apply { - val returnIsLargeFormFactorIndex = instructions.lastIndex - 4 - val returnIsLargeFormFactorLabel = getInstruction(returnIsLargeFormFactorIndex) - - addInstructionsWithLabels( - 0, - """ - invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getTabletLayoutEnabled()Z - move-result v0 - if-nez v0, :is_large_form_factor - """, - ExternalLabel( - "is_large_form_factor", - returnIsLargeFormFactorLabel, - ), - ) - } - } -} +@Deprecated("Use 'Change form factor' instead.") +val enableTabletLayoutPatch = bytecodePatch { + dependsOn(changeFormFactorPatch) +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/Fingerprints.kt deleted file mode 100644 index 30667a85bf..0000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/tablet/Fingerprints.kt +++ /dev/null @@ -1,25 +0,0 @@ -package app.revanced.patches.youtube.layout.tablet - -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.AccessFlags -import app.revanced.patcher.fingerprint - -internal val getFormFactorFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) - returns("L") - parameters("Landroid/content/Context;", "Ljava/util/List;") - opcodes( - Opcode.SGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.IF_EQZ, - Opcode.SGET_OBJECT, - Opcode.RETURN_OBJECT, - Opcode.INVOKE_STATIC, - Opcode.MOVE_RESULT_OBJECT, - Opcode.RETURN_OBJECT, - ) - strings("") -} diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index d317a36b79..e791b07fd8 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -8,7 +8,7 @@ iOS TV - + ANDROID_VR ANDROID_VR_NO_AUTH ANDROID_UNPLUGGED @@ -148,6 +148,19 @@ 17.33.42 + + + @string/revanced_change_form_factor_entry_1 + @string/revanced_change_form_factor_entry_2 + @string/revanced_change_form_factor_entry_3 + + + + DEFAULT + SMALL + LARGE + + @string/revanced_miniplayer_type_entry_0 @@ -159,7 +172,7 @@ @string/revanced_miniplayer_type_entry_6 - + DISABLED DEFAULT MINIMAL @@ -177,7 +190,7 @@ @string/revanced_miniplayer_type_entry_6 - + DEFAULT MINIMAL TABLET @@ -206,7 +219,7 @@ @string/revanced_change_start_page_entry_browse - + DEFAULT SEARCH @@ -233,7 +246,7 @@ @string/revanced_shorts_player_type_regular_player - + SHORTS_PLAYER REGULAR_PLAYER @@ -257,7 +270,7 @@ @string/revanced_alt_thumbnail_options_entry_4 - + ORIGINAL DEARROW DEARROW_STILL_IMAGES diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 83737d8c11..a00f725642 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1019,6 +1019,12 @@ Ready to submit?" sponsor.ajay.app Data is provided by the SponsorBlock API. Tap here to learn more and see downloads for other platforms + + Layout form factor + Default + Phone + Tablet + Spoof app version Version spoofed @@ -1076,12 +1082,6 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. Shorts background play will autoplay Shorts background play will repeat - - Enable tablet layout - Tablet layout is enabled - Tablet layout is disabled - Community posts do not show up on tablet layouts - x Miniplayer Change the style of the in app minimized player From 15150e6c24db5035c9e0b97122d856a2d40ee712 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Thu, 26 Dec 2024 04:14:39 +0400 Subject: [PATCH 2/8] Add Automotive form factor --- .../extension/youtube/patches/ChangeFormFactorPatch.java | 5 +++-- .../patches/youtube/layout/formfactor/Fingerprints.kt | 3 ++- patches/src/main/resources/addresources/values/arrays.xml | 2 ++ patches/src/main/resources/addresources/values/strings.xml | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java index de083203a2..a974f322bc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java @@ -14,9 +14,10 @@ public enum FormFactor { */ DEFAULT(null), /** + *
          * Some changes include:
-         * - Explore tab is present
-         * - watch history is missing
+         * - Explore tab is present.
+         * - watch history is missing.
          * - feed thumbnails fade in.
          */
         UNKNOWN(0),
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt
index 230b7933e0..d1f1535ebf 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/Fingerprints.kt
@@ -13,7 +13,8 @@ internal val formFactorEnumConstructorFingerprint = fingerprint {
     strings(
         "UNKNOWN_FORM_FACTOR",
         "SMALL_FORM_FACTOR",
-        "LARGE_FORM_FACTOR"
+        "LARGE_FORM_FACTOR",
+        "AUTOMOTIVE_FORM_FACTOR"
     )
 }
 
diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml
index e791b07fd8..a10e4a6d34 100644
--- a/patches/src/main/resources/addresources/values/arrays.xml
+++ b/patches/src/main/resources/addresources/values/arrays.xml
@@ -153,12 +153,14 @@
                 @string/revanced_change_form_factor_entry_1
                 @string/revanced_change_form_factor_entry_2
                 @string/revanced_change_form_factor_entry_3
+                @string/revanced_change_form_factor_entry_4
             
             
                 
                 DEFAULT
                 SMALL
                 LARGE
+                AUTOMOTIVE
             
         
         
diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml
index a00f725642..fcc0819f4f 100644
--- a/patches/src/main/resources/addresources/values/strings.xml
+++ b/patches/src/main/resources/addresources/values/strings.xml
@@ -1024,6 +1024,7 @@ Ready to submit?"
             Default
             Phone
             Tablet
+            Automotive
         
         
             Spoof app version

From 7a17a771388c0e9f13dc5cbea570e012e5f2d4ed Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 11:11:57 +0400
Subject: [PATCH 3/8] Adjust text

---
 .../patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
index ee32212982..b7621b02f4 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
@@ -20,7 +20,7 @@ internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube
 @Suppress("unused")
 val changeFormFactorPatch = bytecodePatch(
     name = "Change form factor",
-    description = "Adds an option to change the UI appearance to a tablet or phone.",
+    description = "Adds an option to change the UI appearance to a phone, tablet, or automotive device.",
 ) {
     dependsOn(
         sharedExtensionPatch,

From 5a6097b4d7ca51f13914d33a2dfc6b2daab4db30 Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 19:05:02 +0400
Subject: [PATCH 4/8] Fix missing cairo filled notification icon

Code adapted from https://github.com/inotia00/revanced-patches/commit/831d2a1e76ed757b080ccae24b83bc380befb438
---
 .../youtube/shared/NavigationBar.java         | 27 ++++++++++++++
 .../youtube/misc/navigation/Fingerprints.kt   | 12 ++++++
 .../misc/navigation/NavigationBarHookPatch.kt | 37 +++++++++++++++++++
 3 files changed, 76 insertions(+)

diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
index 82dd83d958..a302a59baf 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
@@ -3,12 +3,15 @@
 import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
 
 import android.app.Activity;
+import android.os.Build;
 import android.view.View;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.WeakHashMap;
@@ -242,6 +245,30 @@ private static void navigationTabCreatedCallback(NavigationButton button, View t
         // Code is added during patching.
     }
 
+    /**
+     * Use the bundled non cairo filled icon instead of a custom icon.
+     * Use the old non cairo filled icon, which is almost identical to
+     * the what would be the filled cairo icon.
+     */
+    private static final int fillBellCairoBlack = Utils.getResourceIdentifier(
+            "yt_fill_bell_black_24", "drawable");
+
+    /**
+     * Injection point.
+     * Fixes missing drawable.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
+        if (fillBellCairoBlack != 0) {
+            // Show a popup informing this fix is no longer needed to those who might care.
+            if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
+                Logger.printException(() -> "YouTube fixed the cairo notification icons");
+            }
+            enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
+        }
+    }
+
     public enum NavigationButton {
         HOME("PIVOT_HOME", "TAB_HOME_CAIRO"),
         SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/Fingerprints.kt
index 0f5ade30a6..c64cf75940 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/Fingerprints.kt
@@ -105,3 +105,15 @@ internal val pivotBarConstructorFingerprint = fingerprint {
     accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
     strings("com.google.android.apps.youtube.app.endpoint.flags")
 }
+
+internal val imageEnumConstructorFingerprint = fingerprint {
+    accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
+    strings("TAB_ACTIVITY_CAIRO")
+}
+
+internal val setEnumMapFingerprint = fingerprint {
+    accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
+    literal {
+        ytFillBellId
+    }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
index 503eb91d41..902567eb89 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
@@ -1,6 +1,7 @@
 package app.revanced.patches.youtube.misc.navigation
 
 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.extensions.InstructionExtensions.instructions
 import app.revanced.patcher.patch.PatchException
@@ -12,13 +13,16 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
 import app.revanced.patches.shared.misc.mapping.resourceMappings
 import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
 import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
+import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater
 import app.revanced.util.getReference
 import app.revanced.util.indexOfFirstInstructionOrThrow
+import app.revanced.util.indexOfFirstInstructionReversedOrThrow
 import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
 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.instruction.ReferenceInstruction
 import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 import com.android.tools.smali.dexlib2.util.MethodUtil
 
@@ -26,6 +30,8 @@ internal var imageOnlyTabResourceId = -1L
     private set
 internal var actionBarSearchResultsViewMicId = -1L
     private set
+internal var ytFillBellId = -1L
+    private set
 
 private val navigationBarHookResourcePatch = resourcePatch {
     dependsOn(resourceMappingPatch)
@@ -33,6 +39,7 @@ private val navigationBarHookResourcePatch = resourcePatch {
     execute {
         imageOnlyTabResourceId = resourceMappings["layout", "image_only_tab"]
         actionBarSearchResultsViewMicId = resourceMappings["layout", "action_bar_search_results_view_mic"]
+        ytFillBellId = resourceMappings["drawable", "yt_fill_bell_black_24"]
     }
 }
 
@@ -144,6 +151,36 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
                     "(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
             )
         }
+
+        // Fix YT bug of notification tab missing the filled icon.
+        if (is_19_35_or_greater) {
+            val cairoNotificationEnumReference = with(imageEnumConstructorFingerprint) {
+                val stringIndex = stringMatches!!.first().index
+                val cairoNotificationEnumIndex = method.indexOfFirstInstructionOrThrow(stringIndex) {
+                    opcode == Opcode.SPUT_OBJECT
+                }
+                method.getInstruction(cairoNotificationEnumIndex).reference
+            }
+
+            setEnumMapFingerprint.method.apply {
+                val enumMapIndex = indexOfFirstInstructionReversedOrThrow {
+                    val reference = getReference()
+                    opcode == Opcode.INVOKE_VIRTUAL &&
+                            reference?.definingClass == "Ljava/util/EnumMap;" &&
+                            reference.name == "put" &&
+                            reference.parameterTypes.firstOrNull() == "Ljava/lang/Enum;"
+                }
+                val instruction = getInstruction(enumMapIndex)
+
+                addInstructions(
+                    enumMapIndex + 1,
+                    """
+                        sget-object v${instruction.registerD}, $cairoNotificationEnumReference
+                        invoke-static { v${instruction.registerC}, v${instruction.registerD} }, $EXTENSION_CLASS_DESCRIPTOR->setCairoNotificationFilledIcon(Ljava/util/EnumMap;Ljava/lang/Enum;)V
+                    """
+                )
+            }
+        }
     }
 }
 

From 2fad1020d01922731f2910b9b89ac1e93df1b214 Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 19:07:09 +0400
Subject: [PATCH 5/8] fix rebase

---
 .../java/app/revanced/extension/youtube/settings/Settings.java   | 1 +
 1 file changed, 1 insertion(+)

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 389c873770..19f109993a 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
@@ -9,6 +9,7 @@
 import static app.revanced.extension.shared.settings.Setting.parentsAny;
 import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
 import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
+import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
 import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
 import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
 import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;

From 48916c4a6361fccc33ad876a840f5171eed361d1 Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 20:01:35 +0400
Subject: [PATCH 6/8] fix: Add user dialog to all other preference types

---
 .../extension/shared/settings/Setting.java    |  3 +-
 .../AbstractPreferenceFragment.java           | 46 ++++++++++++++-----
 .../extension/youtube/settings/Settings.java  |  2 +-
 .../resources/addresources/values/strings.xml | 11 +++++
 4 files changed, 49 insertions(+), 13 deletions(-)

diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java
index 8ce4a9fa76..78852d3700 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java
@@ -153,7 +153,6 @@ private static List> allLoadedSettingsSorted() {
 
     /**
      * Confirmation message to display, if the user tries to change the setting from the default value.
-     * Currently this works only for Boolean setting types.
      */
     @Nullable
     public final StringRef userDialogMessage;
@@ -244,6 +243,7 @@ public static  void migrateOldSettingToNew(@NonNull Setting oldSetting, @N
      *
      * This method will be deleted in the future.
      */
+    @SuppressWarnings("rawtypes")
     public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
         if (!oldPrefs.preferences.contains(settingKey)) {
             return; // Nothing to do.
@@ -419,6 +419,7 @@ public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNu
 
             boolean rebootSettingChanged = false;
             int numberOfSettingsImported = 0;
+            //noinspection rawtypes
             for (Setting setting : SETTINGS) {
                 String key = setting.getImportExportKey();
                 if (json.has(key)) {
diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
index 902b95897e..b35e5623e8 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
@@ -35,6 +35,33 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
     @Nullable
     protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
 
+    private static boolean prefIsSetToDefault(Preference pref, Setting setting) {
+        if (pref instanceof SwitchPreference switchPref) {
+            return switchPref.isChecked() == (Boolean) setting.defaultValue;
+        }
+        if (pref instanceof EditTextPreference editPreference) {
+            return editPreference.getText().equals(setting.defaultValue.toString());
+        }
+        if (pref instanceof ListPreference listPref) {
+            return listPref.getValue().equals(setting.defaultValue.toString());
+        }
+
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Recursive call that also sets the Setting value.
+     */
+    private static void applyDefaultToPreference(Preference pref, Setting setting) {
+        if (pref instanceof SwitchPreference switchPref) {
+            switchPref.setChecked((Boolean) setting.defaultValue);
+        } else if (pref instanceof EditTextPreference editPreference) {
+            editPreference.setText(setting.defaultValue.toString());
+        } else if (pref instanceof ListPreference listPref) {
+            listPref.setValue(setting.defaultValue.toString());
+        }
+    }
+
     /**
      * Used to prevent showing reboot dialog, if user cancels a setting user dialog.
      */
@@ -42,7 +69,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
 
     private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
         try {
-            Setting setting = Setting.getSettingFromPath(str);
+            Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
             if (setting == null) {
                 return;
             }
@@ -62,8 +89,8 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
             }
 
             if (!showingUserDialogMessage) {
-                if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
-                    showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting);
+                if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) {
+                    showSettingUserDialogConfirmation(pref, setting);
                 } else if (setting.rebootApp) {
                     showRestartDialog(getContext());
                 }
@@ -92,7 +119,7 @@ protected void initialize() {
         Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
     }
 
-    private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
+    private void showSettingUserDialogConfirmation(Preference pref, Setting setting) {
         Utils.verifyOnMainThread();
 
         final var context = getContext();
@@ -109,7 +136,7 @@ private void showSettingUserDialogConfirmation(SwitchPreference switchPref, Bool
                     }
                 })
                 .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
-                    switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
+                    applyDefaultToPreference(pref, setting);
                 })
                 .setOnDismissListener(dialog -> {
                     showingUserDialogMessage = false;
@@ -170,23 +197,20 @@ private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
     protected void syncSettingWithPreference(@NonNull Preference pref,
                                              @NonNull Setting setting,
                                              boolean applySettingToPreference) {
-        if (pref instanceof SwitchPreference) {
-            SwitchPreference switchPref = (SwitchPreference) pref;
+        if (pref instanceof SwitchPreference switchPref) {
             BooleanSetting boolSetting = (BooleanSetting) setting;
             if (applySettingToPreference) {
                 switchPref.setChecked(boolSetting.get());
             } else {
                 BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
             }
-        } else if (pref instanceof EditTextPreference) {
-            EditTextPreference editPreference = (EditTextPreference) pref;
+        } else if (pref instanceof EditTextPreference editPreference) {
             if (applySettingToPreference) {
                 editPreference.setText(setting.get().toString());
             } else {
                 Setting.privateSetValueFromString(setting, editPreference.getText());
             }
-        } else if (pref instanceof ListPreference) {
-            ListPreference listPref = (ListPreference) pref;
+        } else if (pref instanceof ListPreference listPref) {
             if (applySettingToPreference) {
                 listPref.setValue(setting.get().toString());
             } else {
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 19f109993a..511d129c80 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
@@ -201,7 +201,7 @@ public class Settings extends BaseSettings {
     public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
 
     // General layout
-    public static final EnumSetting CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true);
+    public static final EnumSetting CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
     public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
     public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
     public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml
index fcc0819f4f..66eed69439 100644
--- a/patches/src/main/resources/addresources/values/strings.xml
+++ b/patches/src/main/resources/addresources/values/strings.xml
@@ -1025,6 +1025,17 @@ Ready to submit?"
             Phone
             Tablet
             Automotive
+            "Changes include:
+
+Tablet layout
+• Community posts are hidden
+
+Automotive layout
+• Explore tab is restored
+• Shorts open in the regular player
+• Home feed is organized by topics
+• Subscriptions is organized by channel
+• Watch history menu is hidden"
         
         
             Spoof app version

From 0cb6e11fb9d78f9f762ea4b58d1ee22e2b1f33a4 Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 20:29:01 +0400
Subject: [PATCH 7/8] fix: If user cancels dialog, revert to previous value

---
 .../AbstractPreferenceFragment.java           | 68 ++++++++-----------
 .../resources/addresources/values/strings.xml |  5 +-
 2 files changed, 32 insertions(+), 41 deletions(-)

diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
index b35e5623e8..43a1e62af1 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
@@ -35,33 +35,6 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
     @Nullable
     protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
 
-    private static boolean prefIsSetToDefault(Preference pref, Setting setting) {
-        if (pref instanceof SwitchPreference switchPref) {
-            return switchPref.isChecked() == (Boolean) setting.defaultValue;
-        }
-        if (pref instanceof EditTextPreference editPreference) {
-            return editPreference.getText().equals(setting.defaultValue.toString());
-        }
-        if (pref instanceof ListPreference listPref) {
-            return listPref.getValue().equals(setting.defaultValue.toString());
-        }
-
-        throw new IllegalStateException();
-    }
-
-    /**
-     * Recursive call that also sets the Setting value.
-     */
-    private static void applyDefaultToPreference(Preference pref, Setting setting) {
-        if (pref instanceof SwitchPreference switchPref) {
-            switchPref.setChecked((Boolean) setting.defaultValue);
-        } else if (pref instanceof EditTextPreference editPreference) {
-            editPreference.setText(setting.defaultValue.toString());
-        } else if (pref instanceof ListPreference listPref) {
-            listPref.setValue(setting.defaultValue.toString());
-        }
-    }
-
     /**
      * Used to prevent showing reboot dialog, if user cancels a setting user dialog.
      */
@@ -79,23 +52,21 @@ private static void applyDefaultToPreference(Preference pref, Setting setting
             }
             Logger.printDebug(() -> "Preference changed: " + setting.key);
 
-            // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
-            updatePreference(pref, setting, true, settingImportInProgress);
-            // Update any other preference availability that may now be different.
-            updateUIAvailability();
-
-            if (settingImportInProgress) {
-                return;
-            }
-
-            if (!showingUserDialogMessage) {
+            if (!settingImportInProgress && !showingUserDialogMessage) {
                 if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) {
+                    // Do not change the setting yet, to allow preserving whatever
+                    // list/text value was previously set if it needs to be reverted.
                     showSettingUserDialogConfirmation(pref, setting);
+                    return;
                 } else if (setting.rebootApp) {
                     showRestartDialog(getContext());
                 }
             }
 
+            // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
+            updatePreference(pref, setting, true, settingImportInProgress);
+            // Update any other preference availability that may now be different.
+            updateUIAvailability();
         } catch (Exception ex) {
             Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
         }
@@ -131,12 +102,19 @@ private void showSettingUserDialogConfirmation(Preference pref, Setting setti
                 .setTitle(confirmDialogTitle)
                 .setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
                 .setPositiveButton(android.R.string.ok, (dialog, id) -> {
+                    // User confirmed, save to the Setting.
+                    updatePreference(pref, setting, true, false);
+
+                    // Update availability of other preferences that may be changed.
+                    updateUIAvailability();
+
                     if (setting.rebootApp) {
                         showRestartDialog(context);
                     }
                 })
                 .setNegativeButton(android.R.string.cancel, (dialog, id) -> {
-                    applyDefaultToPreference(pref, setting);
+                    // Restore whatever the setting was before the change.
+                    updatePreference(pref, setting, true, true);
                 })
                 .setOnDismissListener(dialog -> {
                     showingUserDialogMessage = false;
@@ -159,6 +137,20 @@ protected void updateUIAvailability() {
         updatePreferenceScreen(getPreferenceScreen(), false, false);
     }
 
+    protected boolean prefIsSetToDefault(Preference pref, Setting setting) {
+        if (pref instanceof SwitchPreference switchPref) {
+            return switchPref.isChecked() == (Boolean) setting.defaultValue;
+        }
+        if (pref instanceof EditTextPreference editPreference) {
+            return editPreference.getText().equals(setting.defaultValue.toString());
+        }
+        if (pref instanceof ListPreference listPref) {
+            return listPref.getValue().equals(setting.defaultValue.toString());
+        }
+
+        throw new IllegalStateException();
+    }
+
     /**
      * Syncs all UI Preferences to any {@link Setting} they represent.
      */
diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml
index 66eed69439..6932bd9e00 100644
--- a/patches/src/main/resources/addresources/values/strings.xml
+++ b/patches/src/main/resources/addresources/values/strings.xml
@@ -1031,11 +1031,10 @@ Tablet layout
 • Community posts are hidden
 
 Automotive layout
+• Watch history menu is hidden
 • Explore tab is restored
 • Shorts open in the regular player
-• Home feed is organized by topics
-• Subscriptions is organized by channel
-• Watch history menu is hidden"
+• Feed is organized by topics and channel"
         
         
             Spoof app version

From 5f355c885e372da333d9ba535cee944d0dcae49c Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Thu, 26 Dec 2024 20:32:53 +0400
Subject: [PATCH 8/8] comments

---
 .../settings/preference/AbstractPreferenceFragment.java     | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
index 43a1e62af1..c7b570a855 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java
@@ -137,6 +137,9 @@ protected void updateUIAvailability() {
         updatePreferenceScreen(getPreferenceScreen(), false, false);
     }
 
+    /**
+     * @return If the preference is currently set to the default value of the Setting.
+     */
     protected boolean prefIsSetToDefault(Preference pref, Setting setting) {
         if (pref instanceof SwitchPreference switchPref) {
             return switchPref.isChecked() == (Boolean) setting.defaultValue;
@@ -148,7 +151,8 @@ protected boolean prefIsSetToDefault(Preference pref, Setting setting) {
             return listPref.getValue().equals(setting.defaultValue.toString());
         }
 
-        throw new IllegalStateException();
+        throw new IllegalStateException("Must override method to handle "
+                + "preference type: " + pref.getClass());
     }
 
     /**