From d3f8fb97399aafe98a4198234338c6d0196a7e75 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sun, 30 Apr 2023 23:41:13 +0400 Subject: [PATCH] fix(youtube/return-youtube-dislike): support old UI layouts (#378) --- .../patches/ReturnYouTubeDislikePatch.java | 94 ++++++++++++++++++- .../ReturnYouTubeDislike.java | 22 +++-- .../ReturnYouTubeDislikeSettingsFragment.java | 30 ++---- 3 files changed, 118 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index 690e3e48..695b4afe 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -2,23 +2,114 @@ package app.revanced.integrations.patches; import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; +import android.text.Editable; import android.text.SpannableString; import android.text.Spanned; +import android.text.TextWatcher; +import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicReference; import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; public class ReturnYouTubeDislikePatch { + /** + * Resource identifier of old UI dislike button. + */ + private static final int OLD_UI_DISLIKE_BUTTON_RESOURCE_ID + = ReVancedUtils.getResourceIdentifier("dislike_button", "id"); + + /** + * Dislikes text label used by old UI. + */ + @NonNull + private static WeakReference oldUITextViewRef = new WeakReference<>(null); + + /** + * Original old UI 'Dislikes' text before patch modifications. + * Required to reset the dislikes when changing videos and RYD is not available. + * Set only once during the first load. + */ + private static Spanned oldUIOriginalSpan; + + /** + * Replacement span that contains dislike value. Used by {@link #oldUiTextWatcher}. + */ + @Nullable + private static Spanned oldUIReplacementSpan; + + /** + * Old UI dislikes can be set multiple times by YouTube. + * To prevent it from reverting changes made here, this listener overrides any future changes YouTube makes. + */ + private static final TextWatcher oldUiTextWatcher = new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + public void afterTextChanged(Editable s) { + if (oldUIReplacementSpan == null || oldUIReplacementSpan.toString().equals(s.toString())) { + return; + } + s.replace(0, s.length(), oldUIReplacementSpan); + } + }; + + private static void updateOldUIDislikesTextView() { + TextView oldUITextView = oldUITextViewRef.get(); + if (oldUITextView == null) { + return; + } + Spanned dislikes = ReturnYouTubeDislike.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false); + if (dislikes == null) { // Dislikes not available. + // Must reset text back to original as the TextView may contain dislikes of a prior video. + dislikes = oldUIOriginalSpan; + } + oldUIReplacementSpan = dislikes; + if (!dislikes.equals(oldUITextView.getText())) { + oldUITextView.setText(dislikes); + } + } + + /** + * Injection point. Called on main thread. + * + * Used when spoofing the older app versions of {@link SpoofAppVersionPatch}. + */ + public static void setOldUILayoutDislikes(int buttonViewResourceId, @NonNull TextView textView) { + try { + if (!SettingsEnum.RYD_ENABLED.getBoolean() + || buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID) { + return; + } + if (oldUIOriginalSpan == null) { + // Use value of the first instance, as it appears TextViews can be recycled + // and might contain dislikes previously added by the patch. + oldUIOriginalSpan = (Spanned) textView.getText(); + } + oldUITextViewRef = new WeakReference<>(textView); + // No way to check if a listener is already attached, so remove and add again. + textView.removeTextChangedListener(oldUiTextWatcher); + textView.addTextChangedListener(oldUiTextWatcher); + + updateOldUIDislikesTextView(); + } catch (Exception ex) { + LogHelper.printException(() -> "setOldUILayoutDislikes failure", ex); + } + } + /** * Injection point. */ - public static void newVideoLoaded(String videoId) { + public static void newVideoLoaded(@NonNull String videoId) { try { if (!SettingsEnum.RYD_ENABLED.getBoolean()) return; ReturnYouTubeDislike.newVideoLoaded(videoId); @@ -97,6 +188,7 @@ public class ReturnYouTubeDislikePatch { for (Vote v : Vote.values()) { if (v.value == vote) { ReturnYouTubeDislike.sendVote(v); + updateOldUIDislikesTextView(); return; } } diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java index bc950f70..a170efbf 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -76,7 +76,7 @@ public class ReturnYouTubeDislike { /** * If {@link #currentVideoId} and the RYD data is for the last shorts loaded. */ - private static volatile boolean lastVideoLoadedWasShort; + private static volatile boolean dislikeDataIsShort; /** * Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes. @@ -141,7 +141,7 @@ public class ReturnYouTubeDislike { LogHelper.printDebug(() -> "Clearing data"); } currentVideoId = videoId; - lastVideoLoadedWasShort = false; + dislikeDataIsShort = false; voteFetchFuture = null; originalDislikeSpan = null; replacementLikeDislikeSpan = null; @@ -198,7 +198,7 @@ public class ReturnYouTubeDislike { // If a Short is opened while a regular video is on screen, this will incorrectly set this as false. // But this check is needed to fix unusual situations of opening/closing the app // while both a regular video and a short are on screen. - lastVideoLoadedWasShort = PlayerType.getCurrent().isNoneOrHidden(); + dislikeDataIsShort = PlayerType.getCurrent().isNoneOrHidden(); // No need to wrap the call in a try/catch, // as any exceptions are propagated out in the later Future#Get call. @@ -224,7 +224,15 @@ public class ReturnYouTubeDislike { return null; } - if (lastVideoLoadedWasShort) { + return getDislikesSpanForRegularVideo((Spannable) original, isSegmentedButton); + } + + /** + * @return NULL if the span does not need changing or if RYD is not available. + */ + @Nullable + public static SpannableString getDislikesSpanForRegularVideo(@NonNull Spanned original, boolean isSegmentedButton) { + if (dislikeDataIsShort) { // user: // 1, opened a video // 2. opened a short (without closing the regular video) @@ -234,14 +242,14 @@ public class ReturnYouTubeDislike { return null; } - return waitForFetchAndUpdateReplacementSpan((Spannable) original, isSegmentedButton); + return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton); } /** * Called when a Shorts dislike Spannable is created. */ public static SpannableString getDislikeSpanForShort(@NonNull Spanned original) { - lastVideoLoadedWasShort = true; // it's now certain the video and data are a short + dislikeDataIsShort = true; // it's now certain the video and data are a short return waitForFetchAndUpdateReplacementSpan(original, false); } @@ -313,7 +321,7 @@ public class ReturnYouTubeDislike { try { // Must make a local copy of videoId, since it may change between now and when the vote thread runs. String videoIdToVoteFor = getCurrentVideoId(); - if (videoIdToVoteFor == null || lastVideoLoadedWasShort != PlayerType.getCurrent().isNoneOrHidden()) { + if (videoIdToVoteFor == null || dislikeDataIsShort != PlayerType.getCurrent().isNoneOrHidden()) { // User enabled RYD after starting playback of a video. // Or shorts was loaded with regular video present, then shorts was closed, // and then user voted on the now visible original video. diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java b/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java index 3175675e..6a8a0bb2 100644 --- a/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java @@ -20,33 +20,17 @@ import app.revanced.integrations.settings.SharedPrefCategory; public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { /** - * If ReturnYouTubeDislike is enabled - */ - private SwitchPreference enabledPreference; - - /** - * If dislikes are shown as percentage + * If dislikes are shown as percentage. */ private SwitchPreference percentagePreference; /** - * If segmented like/dislike button uses smaller compact layout + * If segmented like/dislike button uses smaller compact layout. */ private SwitchPreference compactLayoutPreference; private void updateUIState() { - enabledPreference.setSummary(SettingsEnum.RYD_ENABLED.getBoolean() - ? str("revanced_ryd_enable_summary_on") - : str("revanced_ryd_enable_summary_off")); - - percentagePreference.setSummary(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean() - ? str("revanced_ryd_dislike_percentage_summary_on") - : str("revanced_ryd_dislike_percentage_summary_off")); percentagePreference.setEnabled(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.isAvailable()); - - compactLayoutPreference.setSummary(SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean() - ? str("revanced_ryd_compact_layout_summary_on") - : str("revanced_ryd_compact_layout_summary_off")); compactLayoutPreference.setEnabled(SettingsEnum.RYD_USE_COMPACT_LAYOUT.isAvailable()); } @@ -59,9 +43,11 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context); setPreferenceScreen(preferenceScreen); - enabledPreference = new SwitchPreference(context); + SwitchPreference enabledPreference = new SwitchPreference(context); enabledPreference.setChecked(SettingsEnum.RYD_ENABLED.getBoolean()); enabledPreference.setTitle(str("revanced_ryd_enable_title")); + enabledPreference.setSummaryOn(str("revanced_ryd_enable_summary_on")); + enabledPreference.setSummaryOff(str("revanced_ryd_enable_summary_off")); enabledPreference.setOnPreferenceChangeListener((pref, newValue) -> { final boolean rydIsEnabled = (Boolean) newValue; SettingsEnum.RYD_ENABLED.saveValue(rydIsEnabled); @@ -75,6 +61,8 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { percentagePreference = new SwitchPreference(context); percentagePreference.setChecked(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean()); percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title")); + percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on")); + percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off")); percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> { SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.saveValue(newValue); ReturnYouTubeDislike.clearCache(); @@ -86,6 +74,8 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { compactLayoutPreference = new SwitchPreference(context); compactLayoutPreference.setChecked(SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean()); compactLayoutPreference.setTitle(str("revanced_ryd_compact_layout_title")); + compactLayoutPreference.setSummaryOn(str("revanced_ryd_compact_layout_summary_on")); + compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off")); compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> { SettingsEnum.RYD_USE_COMPACT_LAYOUT.saveValue(newValue); ReturnYouTubeDislike.clearCache(); @@ -185,7 +175,7 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { } } - private String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) { + private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) { if (value == 0) { return str(summaryStringZeroKey); }