From 070c4f983a146178b30d38a0e35719104c510dc5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 13 Nov 2023 01:44:43 +0100 Subject: [PATCH 01/21] feat(YouTube - Return YouTube Dislike): Support version `18.43.45` --- .../patches/ReturnYouTubeDislikePatch.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 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 cd5ef761bf..6818b35906 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -1,19 +1,18 @@ package app.revanced.integrations.patches; -import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; - import android.graphics.Rect; import android.os.Build; -import android.text.Editable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextWatcher; +import android.text.*; import android.view.View; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; +import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.shared.PlayerType; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -21,12 +20,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; -import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.shared.PlayerType; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; +import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; /** * Handles all interaction of UI patch components. @@ -197,12 +191,12 @@ public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable Te * @param textRef Cache reference to the like/dislike char sequence, * which may or may not be the same as the original span parameter. * If dislikes are added, the atomic reference must be set to the replacement span. - * @param original Original span that was created or reused by Litho. - * @return The original span (if nothing should change), or a replacement span that contains dislikes. + * @param original Original char sequence was created or reused by Litho. + * @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes. */ @NonNull public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, - @NonNull AtomicReference textRef, + AtomicReference textRef, @NonNull CharSequence original) { try { if (!SettingsEnum.RYD_ENABLED.getBoolean()) { @@ -213,14 +207,21 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, // Remove this log statement after the a/b new litho dislikes is fixed. LogHelper.printDebug(() -> "conversionContext: " + conversionContextString); - final Spanned replacement; + final CharSequence replacement; if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) { // Regular video ReturnYouTubeDislike videoData = currentVideoData; if (videoData == null) { return original; // User enabled RYD while a video was on screen. } - replacement = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); + + // In some cases the original may not be a Spannable, so convert it. + final var isSpannable = original instanceof Spannable; + + original = isSpannable ? (Spannable) original : new SpannableString(original); + final var dislikesSpan = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); + replacement = isSpannable ? dislikesSpan : dislikesSpan.toString(); + // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout but uses litho // and the dislikes is "|dislike_button.eml|" // but spoofing to that range gives a broken UI layout so no point checking for that. @@ -255,7 +256,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, return original; } - textRef.set(replacement); + if (textRef != null) textRef.set(replacement); return replacement; } catch (Exception ex) { LogHelper.printException(() -> "onLithoTextLoaded failure", ex); From 47358467f70c1d8c3c54acdc84f0cf62b835136a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:13:59 +0200 Subject: [PATCH 02/21] cleanup --- .../integrations/patches/ReturnYouTubeDislikePatch.java | 4 +--- 1 file changed, 1 insertion(+), 3 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 6818b35906..11e7a7e505 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -196,7 +196,7 @@ public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable Te */ @NonNull public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, - AtomicReference textRef, + @Nullable AtomicReference textRef, @NonNull CharSequence original) { try { if (!SettingsEnum.RYD_ENABLED.getBoolean()) { @@ -204,8 +204,6 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, } String conversionContextString = conversionContext.toString(); - // Remove this log statement after the a/b new litho dislikes is fixed. - LogHelper.printDebug(() -> "conversionContext: " + conversionContextString); final CharSequence replacement; if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) { From 6ecaed51256888851669cc859f66c00f092502c7 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:46:43 +0200 Subject: [PATCH 03/21] fix: rolling number missing separators and span styling --- .../patches/ReturnYouTubeDislikePatch.java | 80 +++++++++++++++---- .../ReturnYouTubeDislike.java | 6 +- 2 files changed, 67 insertions(+), 19 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 11e7a7e505..ea8f908098 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -69,12 +69,16 @@ public static void onRYDStatusChange(boolean rydEnabled) { if (!rydEnabled) { // Must remove all values to protect against using stale data // if the user enables RYD while a video is on screen. - currentVideoData = null; - lastLithoShortsVideoData = null; - lithoShortsShouldUseCurrentData = false; + clearData(); } } + private static void clearData() { + currentVideoData = null; + lastLithoShortsVideoData = null; + rollingNumberText = null; + lithoShortsShouldUseCurrentData = false; + } // // 17.x non litho regular video player. @@ -212,13 +216,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, if (videoData == null) { return original; // User enabled RYD while a video was on screen. } - - // In some cases the original may not be a Spannable, so convert it. - final var isSpannable = original instanceof Spannable; - - original = isSpannable ? (Spannable) original : new SpannableString(original); - final var dislikesSpan = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); - replacement = isSpannable ? dislikesSpan : dislikesSpan.toString(); + replacement = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout but uses litho // and the dislikes is "|dislike_button.eml|" @@ -229,7 +227,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, // Must clear the current video here, otherwise if the user opens a regular video // then opens a litho short (while keeping the regular video on screen), then closes the short, // the original video may show the incorrect dislike value. - currentVideoData = null; + clearData(); return original; } ReturnYouTubeDislike videoData = lastLithoShortsVideoData; @@ -262,6 +260,58 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, return original; } + // + // Rolling Number + // + + /** + * Current regular video rolling text, if one exists. + * This is saved to a field as it's used in every draw() call. + */ + @Nullable + private static volatile CharSequence rollingNumberText; + + public static CharSequence onRollingNumberLoaded(@NonNull Object conversionContext, + @NonNull CharSequence original) { + try { + if (SettingsEnum.RYD_ENABLED.getBoolean()) { + final boolean isSpannable = original instanceof Spanned; + Spanned originalSpan = isSpannable ? (Spanned) original : new SpannableString(original); + CharSequence replacement = onLithoTextLoaded(conversionContext, null, originalSpan); + rollingNumberText = replacement; + + if (isSpannable) return replacement; + return replacement.toString(); + } + } catch (Exception ex) { + LogHelper.printException(() -> "onRollingNumberLoaded failure", ex); + } + return original; + } + + /** + * Injection point. + */ + public static CharSequence updateRollingText(CharSequence text) { + try { + if (SettingsEnum.RYD_ENABLED.getBoolean()) { + // Called for all instances of RollingNumber, so must check if text is for a dislikes. + // Text will already have the correct content, but it's missing the separators and Span styling. + if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text.toString())) { + return text; + } + CharSequence replacement = rollingNumberText; + if (replacement == null) { + LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); + return text; + } + return rollingNumberText; + } + } catch (Exception ex) { + LogHelper.printException(() -> "updateRollingText failure", ex); + } + return text; + } // // Non litho Shorts player. @@ -300,7 +350,7 @@ public static boolean setShortsDislikes(@NonNull View likeDislikeView) { if (!SettingsEnum.RYD_SHORTS.getBoolean()) { // Must clear the data here, in case a new video was loaded while PlayerType // suggested the video was not a short (can happen when spoofing to an old app version). - currentVideoData = null; + clearData(); return false; } LogHelper.printDebug(() -> "setShortsDislikes"); @@ -443,7 +493,7 @@ public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLith final boolean isNoneHiddenOrSlidingMinimized = currentPlayerType.isNoneHiddenOrSlidingMinimized(); if (isNoneHiddenOrSlidingMinimized && !SettingsEnum.RYD_SHORTS.getBoolean()) { // Must clear here, otherwise the wrong data can be used for a minimized regular video. - currentVideoData = null; + clearData(); return; } @@ -458,9 +508,7 @@ public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLith // Must clear both currently playing and last litho data otherwise the // next regular video may use the wrong data. LogHelper.printDebug(() -> "Litho filter did not find any video ids"); - currentVideoData = null; - lastLithoShortsVideoData = null; - lithoShortsShouldUseCurrentData = false; + clearData(); return; } ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId); 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 9890708622..f82462759e 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -243,8 +243,8 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, } // Alternatively, this could check if the span contains one of the custom created spans, but this is simple and quick. - private static boolean isPreviouslyCreatedSegmentedSpan(@NonNull Spanned span) { - return span.toString().indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1; + public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { + return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1; } /** @@ -481,7 +481,7 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, return replacementLikeDislikeSpan; } } - if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original)) { + if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original.toString())) { // need to recreate using original, as original has prior outdated dislike values if (originalDislikeSpan == null) { // Should never happen. From 572eddc7bb8b75556abea785f2401559dba2f2ec Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:07:29 +0200 Subject: [PATCH 04/21] perf: reduce timeout to prior value (no need to wait the extra 0.5 seconds) --- .../integrations/returnyoutubedislike/ReturnYouTubeDislike.java | 2 +- .../returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 f82462759e..d776d5e0dc 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -69,7 +69,7 @@ public enum Vote { * Must be less than 5 seconds, as per: * https://developer.android.com/topic/performance/vitals/anr */ - private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH = 4500; + private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH = 4000; /** * How long to retain successful RYD fetches. diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java index 9403ee0c2c..8daa380517 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java @@ -38,7 +38,7 @@ public class ReturnYouTubeDislikeApi { * {@link #fetchVotes(String)} HTTP read timeout. * To locally debug and force timeouts, change this to a very small number (ie: 100) */ - private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 5 * 1000; // 5 Seconds. + private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 4 * 1000; // 4 Seconds. /** * Default connection and response timeout for voting and registration. From 0896b81123375e49777eb20db9555c44c71b747e Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:09:56 +0200 Subject: [PATCH 05/21] cleanup and comments --- .../patches/ReturnYouTubeDislikePatch.java | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 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 ea8f908098..72efb4fe9e 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -29,7 +29,7 @@ * Litho based Shorts player can experience temporarily frozen video playback if the RYD fetch takes too long. * * Temporary work around: - * Enable app spoofing to version 18.20.39 or older, as that uses a non litho Shorts player. + * Enable app spoofing to version 18.33.40 or older, as that uses a non litho Shorts player. * * Permanent fix (yet to be implemented), either of: * - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes asynchronously. @@ -192,7 +192,7 @@ public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable Te * This method is sometimes called on the main thread, but it usually is called _off_ the main thread. * This method can be called multiple times for the same UI element (including after dislikes was added). * - * @param textRef Cache reference to the like/dislike char sequence, + * @param textRef Optional cache reference to the like/dislike char sequence, * which may or may not be the same as the original span parameter. * If dislikes are added, the atomic reference must be set to the replacement span. * @param original Original char sequence was created or reused by Litho. @@ -218,9 +218,9 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, } replacement = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); - // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout but uses litho - // and the dislikes is "|dislike_button.eml|" - // but spoofing to that range gives a broken UI layout so no point checking for that. + // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout + // but uses litho and the dislikes is "|dislike_button.eml|". + // But spoofing to that range gives a broken UI layout so no point checking for that. } else if (conversionContextString.contains("|shorts_dislike_button.eml|")) { // Litho Shorts player. if (!SettingsEnum.RYD_SHORTS.getBoolean()) { @@ -271,6 +271,9 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, @Nullable private static volatile CharSequence rollingNumberText; + /** + * Injection point. + */ public static CharSequence onRollingNumberLoaded(@NonNull Object conversionContext, @NonNull CharSequence original) { try { @@ -302,6 +305,8 @@ public static CharSequence updateRollingText(CharSequence text) { } CharSequence replacement = rollingNumberText; if (replacement == null) { + // User enabled RYD while a video was open, + // or user opened/closed a Short while a regular video was opened. LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); return text; } @@ -454,21 +459,25 @@ private static boolean isShortTextViewOnScreen(@NonNull View view) { * Injection point. Uses 'playback response' video id hook to preload RYD. */ public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) { - // Shorts shelf in home and subscription feed causes player response hook to be called, - // and the 'is opening/playing' parameter will be false. - // This hook will be called again when the Short is actually opened. - if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) { - return; - } - if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized()) { - return; - } - if (videoId.equals(lastPrefetchedVideoId)) { - return; + try { + // Shorts shelf in home and subscription feed causes player response hook to be called, + // and the 'is opening/playing' parameter will be false. + // This hook will be called again when the Short is actually opened. + if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) { + return; + } + if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized()) { + return; + } + if (videoId.equals(lastPrefetchedVideoId)) { + return; + } + lastPrefetchedVideoId = videoId; + LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId); + ReturnYouTubeDislike.getFetchForVideoId(videoId); + } catch (Exception ex) { + LogHelper.printException(() -> "preloadVideoId failure", ex); } - lastPrefetchedVideoId = videoId; - LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId); - ReturnYouTubeDislike.getFetchForVideoId(videoId); } /** From 5bd63053c8c283c3e21ad7fdae5c11637cb70ad1 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:16:56 +0200 Subject: [PATCH 06/21] refactor: change method name --- .../integrations/patches/ReturnYouTubeDislikePatch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 72efb4fe9e..a8650cf065 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -295,13 +295,13 @@ public static CharSequence onRollingNumberLoaded(@NonNull Object conversionConte /** * Injection point. */ - public static CharSequence updateRollingText(CharSequence text) { + public static CharSequence updateRollingNumber(CharSequence text) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { // Called for all instances of RollingNumber, so must check if text is for a dislikes. // Text will already have the correct content, but it's missing the separators and Span styling. if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text.toString())) { - return text; + return text; // Text is the video view count, upload time, or some other text. } CharSequence replacement = rollingNumberText; if (replacement == null) { From 89ec3cf68dc2511a61720f285f001b1df4ab2874 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:35:02 +0200 Subject: [PATCH 07/21] refactor: simplify --- .../patches/ReturnYouTubeDislikePatch.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 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 a8650cf065..1dea7a86a8 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -36,6 +36,7 @@ * - Find a way to force Litho to rebuild it's component tree * (and use that hook to force the shorts dislikes to update after the fetch is completed). */ +@SuppressWarnings("unused") public class ReturnYouTubeDislikePatch { /** @@ -265,7 +266,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, // /** - * Current regular video rolling text, if one exists. + * Current regular video rolling number text, if rolling number is in use. * This is saved to a field as it's used in every draw() call. */ @Nullable @@ -274,16 +275,13 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, /** * Injection point. */ - public static CharSequence onRollingNumberLoaded(@NonNull Object conversionContext, - @NonNull CharSequence original) { + public static String onRollingNumberLoaded(@NonNull Object conversionContext, + @NonNull String original) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { - final boolean isSpannable = original instanceof Spanned; - Spanned originalSpan = isSpannable ? (Spanned) original : new SpannableString(original); + Spanned originalSpan = new SpannableString(original); CharSequence replacement = onLithoTextLoaded(conversionContext, null, originalSpan); rollingNumberText = replacement; - - if (isSpannable) return replacement; return replacement.toString(); } } catch (Exception ex) { @@ -313,7 +311,7 @@ public static CharSequence updateRollingNumber(CharSequence text) { return rollingNumberText; } } catch (Exception ex) { - LogHelper.printException(() -> "updateRollingText failure", ex); + LogHelper.printException(() -> "updateRollingNumber failure", ex); } return text; } @@ -536,6 +534,7 @@ public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLith if (isNoneHiddenOrSlidingMinimized) { data.setVideoIdIsShort(true); } + rollingNumberText = null; currentVideoData = data; } From b9489a84f0ebe3b25c6223d4f26dce9c2cc85263 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 02:40:19 +0200 Subject: [PATCH 08/21] cleanup --- .../patches/ReturnYouTubeDislikePatch.java | 101 ++++++++---------- .../ReturnYouTubeDislikeFilterPatch.java | 2 +- 2 files changed, 48 insertions(+), 55 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 1dea7a86a8..d6bdd0e7d4 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -1,18 +1,19 @@ package app.revanced.integrations.patches; +import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; + import android.graphics.Rect; import android.os.Build; -import android.text.*; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextWatcher; import android.view.View; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; -import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.shared.PlayerType; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -20,7 +21,12 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; +import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; +import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.shared.PlayerType; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; /** * Handles all interaction of UI patch components. @@ -482,19 +488,9 @@ public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpenin * Injection point. Uses 'current playing' video id hook. Always called on main thread. */ public static void newVideoLoaded(@NonNull String videoId) { - newVideoLoaded(videoId, false); - } - - /** - * Called both on and off main thread. - * - * @param isShortsLithoVideoId If the video id is from {@link ReturnYouTubeDislikeFilterPatch}. - * if true, then the video id can be null indicating the filter did - * not find any video id. - */ - public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLithoVideoId) { try { if (!SettingsEnum.RYD_ENABLED.getBoolean()) return; + Objects.requireNonNull(videoId); PlayerType currentPlayerType = PlayerType.getCurrent(); final boolean isNoneHiddenOrSlidingMinimized = currentPlayerType.isNoneHiddenOrSlidingMinimized(); @@ -504,46 +500,23 @@ public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLith return; } - if (isShortsLithoVideoId) { - // Litho Shorts video. - if (videoIdIsSame(lastLithoShortsVideoData, videoId)) { - return; - } - if (videoId == null) { - // Litho filter did not detect the video id. App is in incognito mode, - // or the proto buffer structure was changed and the video id is no longer present. - // Must clear both currently playing and last litho data otherwise the - // next regular video may use the wrong data. - LogHelper.printDebug(() -> "Litho filter did not find any video ids"); - clearData(); - return; - } - ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId); - videoData.setVideoIdIsShort(true); - lastLithoShortsVideoData = videoData; - lithoShortsShouldUseCurrentData = false; - } else { - Objects.requireNonNull(videoId); - // All other playback (including non-litho Shorts). - if (videoIdIsSame(currentVideoData, videoId)) { - return; - } - ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId); - // Pre-emptively set the data to short status. - // Required to prevent Shorts data from being used on a minimized video in incognito mode. - if (isNoneHiddenOrSlidingMinimized) { - data.setVideoIdIsShort(true); - } - rollingNumberText = null; - currentVideoData = data; + if (videoIdIsSame(currentVideoData, videoId)) { + return; } + LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType); - LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType - + " isShortsLithoHook: " + isShortsLithoVideoId); + ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId); + // Pre-emptively set the data to short status. + // Required to prevent Shorts data from being used on a minimized video in incognito mode. + if (isNoneHiddenOrSlidingMinimized) { + data.setVideoIdIsShort(true); + } + rollingNumberText = null; + currentVideoData = data; // Current video id hook can be called out of order with the non litho Shorts text view hook. // Must manually update again here. - if (!isShortsLithoVideoId && isNoneHiddenOrSlidingMinimized) { + if (isNoneHiddenOrSlidingMinimized) { updateOnScreenShortsTextViews(true); } } catch (Exception ex) { @@ -551,6 +524,26 @@ public static void newVideoLoaded(@Nullable String videoId, boolean isShortsLith } } + public static void setLastLithoShortsVideoId(@Nullable String videoId) { + if (videoIdIsSame(lastLithoShortsVideoData, videoId)) { + return; + } + if (videoId == null) { + // Litho filter did not detect the video id. App is in incognito mode, + // or the proto buffer structure was changed and the video id is no longer present. + // Must clear both currently playing and last litho data otherwise the + // next regular video may use the wrong data. + LogHelper.printDebug(() -> "Litho filter did not find any video ids"); + clearData(); + return; + } + LogHelper.printDebug(() -> "New litho Shorts video id: " + videoId); + ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId); + videoData.setVideoIdIsShort(true); + lastLithoShortsVideoData = videoData; + lithoShortsShouldUseCurrentData = false; + } + private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) { return (fetch == null && videoId == null) || (fetch != null && fetch.getVideoId().equals(videoId)); diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java index b1ff2d2e18..48c44f36ce 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java @@ -93,7 +93,7 @@ public boolean isFiltered(@Nullable String identifier, String path, byte[] proto // Must pass a null id to correctly clear out the current video data. // Otherwise if a Short is opened in non-incognito, then incognito is enabled and another Short is opened, // the new incognito Short will show the old prior data. - ReturnYouTubeDislikePatch.newVideoLoaded(matchedVideoId, true); + ReturnYouTubeDislikePatch.setLastLithoShortsVideoId(matchedVideoId); } return false; From 41a57111a68e6945306291a9c529c14ad6e78fa3 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 03:07:29 +0200 Subject: [PATCH 09/21] fix over padded likes text if an incognito short is opened/closed while a regular video is on screen --- .../patches/ReturnYouTubeDislikePatch.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 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 d6bdd0e7d4..69b08ce7d8 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -83,8 +83,10 @@ public static void onRYDStatusChange(boolean rydEnabled) { private static void clearData() { currentVideoData = null; lastLithoShortsVideoData = null; - rollingNumberText = null; lithoShortsShouldUseCurrentData = false; + // Rolling number text should not be cleared, + // as it's used if incognito Short is opened/closed + // while a regular video is on screen. } // @@ -218,12 +220,15 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, final CharSequence replacement; if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) { - // Regular video + // Regular video. ReturnYouTubeDislike videoData = currentVideoData; if (videoData == null) { return original; // User enabled RYD while a video was on screen. } - replacement = videoData.getDislikesSpanForRegularVideo((Spannable) original, true); + if (!(original instanceof Spanned)) { + original = new SpannableString(original); + } + replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, true); // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout // but uses litho and the dislikes is "|dislike_button.eml|". @@ -240,7 +245,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, ReturnYouTubeDislike videoData = lastLithoShortsVideoData; if (videoData == null) { // The Shorts litho video id filter did not detect the video id. - // This is normal if in incognito mode, but otherwise is not normal. + // This is normal in incognito mode, but otherwise is abnormal. LogHelper.printDebug(() -> "Cannot modify Shorts litho span, data is null"); return original; } @@ -254,7 +259,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, } LogHelper.printDebug(() -> "Using current video data for litho span"); } - replacement = videoData.getDislikeSpanForShort((Spannable) original); + replacement = videoData.getDislikeSpanForShort((Spanned) original); } else { return original; } @@ -285,10 +290,11 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, @NonNull String original) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { - Spanned originalSpan = new SpannableString(original); - CharSequence replacement = onLithoTextLoaded(conversionContext, null, originalSpan); - rollingNumberText = replacement; - return replacement.toString(); + CharSequence replacement = onLithoTextLoaded(conversionContext, null, original); + if (replacement != original) { + rollingNumberText = replacement; + return replacement.toString(); + } // Else, the text was not a likes count but instead the view count or something else. } } catch (Exception ex) { LogHelper.printException(() -> "onRollingNumberLoaded failure", ex); @@ -511,7 +517,6 @@ public static void newVideoLoaded(@NonNull String videoId) { if (isNoneHiddenOrSlidingMinimized) { data.setVideoIdIsShort(true); } - rollingNumberText = null; currentVideoData = data; // Current video id hook can be called out of order with the non litho Shorts text view hook. From 3777855622b6a250ec6ca3f813a954de92cad007 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:46:30 +0200 Subject: [PATCH 10/21] fix dislikes not showing after opening/closing a Short while a regular video is open --- .../integrations/patches/ReturnYouTubeDislikePatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 69b08ce7d8..dae3afec9d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -291,7 +291,7 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { CharSequence replacement = onLithoTextLoaded(conversionContext, null, original); - if (replacement != original) { + if (!replacement.toString().equals(original)) { rollingNumberText = replacement; return replacement.toString(); } // Else, the text was not a likes count but instead the view count or something else. From 63e8dc7c96c6558495300141e1e09f1916104932 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:14:17 +0200 Subject: [PATCH 11/21] Use a more unique character since this is now searching thru all rolling number usage --- .../returnyoutubedislike/ReturnYouTubeDislike.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 d776d5e0dc..420c24bf4e 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -84,9 +84,12 @@ public enum Vote { /** * Unique placeholder character, used to detect if a segmented span already has dislikes added to it. - * Can be any almost any non-visible character. + * Ideally is something YouTube is unlikely to use, as it's searched for in all usage of Rolling Number. + * + * Character is not visible as it's overwrote with an drawable span, + * but it should be something narrow as it's horizontal space is retained. */ - private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character + private static final char MIDDLE_SEPARATOR_CHARACTER = '⁏'; // 'Reversed Semicolon' /** * Cached lookup of all video ids. @@ -242,9 +245,11 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, return new SpannableString(builder); } - // Alternatively, this could check if the span contains one of the custom created spans, but this is simple and quick. + /** + * @return If the text is likely for a previously created likes/dislikes segmented span. + */ public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { - return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1; + return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0; } /** From c4aa441fc02346f01d603d02d8020b457b0c296a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:35:59 +0200 Subject: [PATCH 12/21] more robust replacing --- .../patches/ReturnYouTubeDislikePatch.java | 13 ++++++------- .../returnyoutubedislike/ReturnYouTubeDislike.java | 9 +++------ 2 files changed, 9 insertions(+), 13 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 dae3afec9d..421ae3a8ab 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -308,19 +308,18 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, public static CharSequence updateRollingNumber(CharSequence text) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { - // Called for all instances of RollingNumber, so must check if text is for a dislikes. - // Text will already have the correct content, but it's missing the separators and Span styling. - if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text.toString())) { - return text; // Text is the video view count, upload time, or some other text. - } CharSequence replacement = rollingNumberText; if (replacement == null) { // User enabled RYD while a video was open, // or user opened/closed a Short while a regular video was opened. - LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); return text; } - return rollingNumberText; + // Called for all instances of RollingNumber. + // Dislikes text will have the correct content, but is missing the styling. Replace with the styled span. + if (!replacement.toString().equals(text.toString())) { + return text; // Text is the video view count, upload time, or some other rolling number text. + } + return replacement; } } catch (Exception ex) { LogHelper.printException(() -> "updateRollingNumber failure", ex); 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 420c24bf4e..aac6267034 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -84,12 +84,9 @@ public enum Vote { /** * Unique placeholder character, used to detect if a segmented span already has dislikes added to it. - * Ideally is something YouTube is unlikely to use, as it's searched for in all usage of Rolling Number. - * - * Character is not visible as it's overwrote with an drawable span, - * but it should be something narrow as it's horizontal space is retained. + * Can be any almost any non-visible character. */ - private static final char MIDDLE_SEPARATOR_CHARACTER = '⁏'; // 'Reversed Semicolon' + private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character /** * Cached lookup of all video ids. @@ -248,7 +245,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, /** * @return If the text is likely for a previously created likes/dislikes segmented span. */ - public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { + private static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0; } From 69c503d4909e5471c40075e051d0df7cb1aa29ed Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:34:33 +0200 Subject: [PATCH 13/21] Revert "more robust replacing" This reverts commit c4aa441fc02346f01d603d02d8020b457b0c296a. --- .../patches/ReturnYouTubeDislikePatch.java | 13 +++++++------ .../returnyoutubedislike/ReturnYouTubeDislike.java | 9 ++++++--- 2 files changed, 13 insertions(+), 9 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 421ae3a8ab..dae3afec9d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -308,18 +308,19 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, public static CharSequence updateRollingNumber(CharSequence text) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { + // Called for all instances of RollingNumber, so must check if text is for a dislikes. + // Text will already have the correct content, but it's missing the separators and Span styling. + if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text.toString())) { + return text; // Text is the video view count, upload time, or some other text. + } CharSequence replacement = rollingNumberText; if (replacement == null) { // User enabled RYD while a video was open, // or user opened/closed a Short while a regular video was opened. + LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); return text; } - // Called for all instances of RollingNumber. - // Dislikes text will have the correct content, but is missing the styling. Replace with the styled span. - if (!replacement.toString().equals(text.toString())) { - return text; // Text is the video view count, upload time, or some other rolling number text. - } - return replacement; + return rollingNumberText; } } catch (Exception ex) { LogHelper.printException(() -> "updateRollingNumber failure", ex); 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 aac6267034..420c24bf4e 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -84,9 +84,12 @@ public enum Vote { /** * Unique placeholder character, used to detect if a segmented span already has dislikes added to it. - * Can be any almost any non-visible character. + * Ideally is something YouTube is unlikely to use, as it's searched for in all usage of Rolling Number. + * + * Character is not visible as it's overwrote with an drawable span, + * but it should be something narrow as it's horizontal space is retained. */ - private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character + private static final char MIDDLE_SEPARATOR_CHARACTER = '⁏'; // 'Reversed Semicolon' /** * Cached lookup of all video ids. @@ -245,7 +248,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, /** * @return If the text is likely for a previously created likes/dislikes segmented span. */ - private static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { + public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0; } From 559e5957a7070307767e4a23d51198138479194c Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:49:26 +0200 Subject: [PATCH 14/21] Fix vertical alignment issues. Fix clipping of dislikes text. --- .../patches/HideBreakingNewsPatch.java | 11 +- .../patches/ReturnYouTubeDislikePatch.java | 121 +++++++++++--- .../patches/spoof/SpoofAppVersionPatch.java | 15 +- .../ReturnYouTubeDislike.java | 153 ++++++++++++++---- .../ReturnYouTubeDislikeSettingsFragment.java | 4 +- 5 files changed, 239 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/HideBreakingNewsPatch.java b/app/src/main/java/app/revanced/integrations/patches/HideBreakingNewsPatch.java index 465c28367d..bb2d76b2f9 100644 --- a/app/src/main/java/app/revanced/integrations/patches/HideBreakingNewsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/HideBreakingNewsPatch.java @@ -2,28 +2,27 @@ import android.view.View; +import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.ReVancedUtils; public class HideBreakingNewsPatch { /** - * When spoofing to app versions older than 17.30.35, the watch history preview bar uses + * When spoofing to app versions 17.31.00 and older, the watch history preview bar uses * the same layout components as the breaking news shelf. * * Breaking news does not appear to be present in these older versions anyways. */ - private static boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory() { - return SettingsEnum.SPOOF_APP_VERSION.getBoolean() - && SettingsEnum.SPOOF_APP_VERSION_TARGET.getString().compareTo("17.30.35") < 0; - } + private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory = + SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("17.31.00"); /** * Injection point. */ public static void hideBreakingNews(View view) { if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean() - || isSpoofingOldVersionWithHorizontalCardListWatchHistory()) return; + || isSpoofingOldVersionWithHorizontalCardListWatchHistory) return; ReVancedUtils.hideViewByLayoutParams(view); } } 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 dae3afec9d..8737030295 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -3,12 +3,14 @@ import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; import android.graphics.Rect; +import android.graphics.drawable.ShapeDrawable; import android.os.Build; import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.TextWatcher; +import android.view.Gravity; import android.view.View; import android.widget.TextView; @@ -144,7 +146,7 @@ private static void updateOldUIDislikesTextView() { if (oldUITextView == null) { return; } - oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false); + oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false, false); if (!oldUIReplacementSpan.equals(oldUITextView.getText())) { oldUITextView.setText(oldUIReplacementSpan); } @@ -195,6 +197,16 @@ public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable Te /** * Injection point. * + * For Litho segmented buttons and Litho Shorts player. + */ + @NonNull + public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, + @Nullable AtomicReference textRef, + @NonNull CharSequence original) { + return onLithoTextLoaded(conversionContext, textRef, original, false); + } + + /** * Called when a litho text component is initially created, * and also when a Span is later reused again (such as scrolling off/on screen). * @@ -205,12 +217,14 @@ public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable Te * which may or may not be the same as the original span parameter. * If dislikes are added, the atomic reference must be set to the replacement span. * @param original Original char sequence was created or reused by Litho. + * @param isRollingNumber If the span is for a Rolling Number. * @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes. */ @NonNull - public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, - @Nullable AtomicReference textRef, - @NonNull CharSequence original) { + private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, + @Nullable AtomicReference textRef, + @NonNull CharSequence original, + boolean isRollingNumber) { try { if (!SettingsEnum.RYD_ENABLED.getBoolean()) { return original; @@ -228,12 +242,13 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, if (!(original instanceof Spanned)) { original = new SpannableString(original); } - replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, true); + replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, + true, isRollingNumber); // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout // but uses litho and the dislikes is "|dislike_button.eml|". // But spoofing to that range gives a broken UI layout so no point checking for that. - } else if (conversionContextString.contains("|shorts_dislike_button.eml|")) { + } else if (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) { // Litho Shorts player. if (!SettingsEnum.RYD_SHORTS.getBoolean()) { // Must clear the current video here, otherwise if the user opens a regular video @@ -281,7 +296,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, * This is saved to a field as it's used in every draw() call. */ @Nullable - private static volatile CharSequence rollingNumberText; + private static volatile CharSequence rollingNumberSpan; /** * Injection point. @@ -290,9 +305,9 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, @NonNull String original) { try { if (SettingsEnum.RYD_ENABLED.getBoolean()) { - CharSequence replacement = onLithoTextLoaded(conversionContext, null, original); + CharSequence replacement = onLithoTextLoaded(conversionContext, null, original, true); if (!replacement.toString().equals(original)) { - rollingNumberText = replacement; + rollingNumberSpan = replacement; return replacement.toString(); } // Else, the text was not a likes count but instead the view count or something else. } @@ -302,30 +317,88 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext, return original; } + /** + * Remove Rolling Number text view modifications made by this patch. + * Required as it appears text views can be reused for other rolling numbers (view count, upload time, etc). + */ + private static void removeRollingNumberPatchChanges(TextView view) { + if (view.getCompoundDrawablePadding() != 0) { + LogHelper.printDebug(() -> "Removing rolling number styling from TextView"); + view.setCompoundDrawablePadding(0); + view.setCompoundDrawables(null, null, null, null); + view.setGravity(Gravity.NO_GRAVITY); + view.setTextAlignment(View.TEXT_ALIGNMENT_INHERIT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + view.setSingleLine(false); + } + } + } + /** * Injection point. */ - public static CharSequence updateRollingNumber(CharSequence text) { + public static CharSequence updateRollingNumber(TextView view, CharSequence original) { try { - if (SettingsEnum.RYD_ENABLED.getBoolean()) { - // Called for all instances of RollingNumber, so must check if text is for a dislikes. - // Text will already have the correct content, but it's missing the separators and Span styling. - if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text.toString())) { - return text; // Text is the video view count, upload time, or some other text. - } - CharSequence replacement = rollingNumberText; - if (replacement == null) { - // User enabled RYD while a video was open, - // or user opened/closed a Short while a regular video was opened. - LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); - return text; + if (!SettingsEnum.RYD_ENABLED.getBoolean()) { + removeRollingNumberPatchChanges(view); + return original; + } + // Called for all instances of RollingNumber, so must check if text is for a dislikes. + // Text will already have the correct content but it's missing the drawable separators. + if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(original.toString())) { + // The text is the video view count, upload time, or some other text. + removeRollingNumberPatchChanges(view); + return original; + } + + CharSequence replacement = rollingNumberSpan; + if (replacement == null) { + // User enabled RYD while a video was open, + // or user opened/closed a Short while a regular video was opened. + LogHelper.printDebug(() -> "Cannot update rolling number (field is null"); + removeRollingNumberPatchChanges(view); + return original; + } + + // TextView does not display the tall left separator correctly, + // as it goes outside the height bounds and messes up the layout. + // Fix this by applying the left separator as a text view compound drawable. + // This create a new issue as the compound drawable is not taken into the + // layout width sizing, but that is fixed in the span itself where it uses a blank + // padding string that adds to the layout width but is later ignored during UI drawing. + if (SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean()) { + // Do not apply any TextView changes, and text should always fit without clipping. + removeRollingNumberPatchChanges(view); + } else if (view.getCompoundDrawablePadding() == 0) { + // YouTube Rolling Numbers do not use compound drawables or drawable padding. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Single line mode prevents entire words from being entirely clipped, + // and instead only clips the portion of text that runs off. + // The text should not clip due to the empty end padding, + // but use the feature if available just in case. + // If making changes to anything, comment this out and verify layout is still correct. + view.setSingleLine(true); } - return rollingNumberText; + // Center align to distribute the horizontal padding. + view.setGravity(Gravity.CENTER); + view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + ShapeDrawable shapeDrawable = ReturnYouTubeDislike.getLeftSeparatorDrawable(); + view.setCompoundDrawables(shapeDrawable, null, null, null); + view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels); } + + // Remove any padding set by Rolling Number. + view.setPadding(0, 0, 0, 0); + + // When displaying dislikes, the rolling animation is not visually correct + // and the dislikes always animate (even though the dislike count has not changed). + // The animation is caused by an image span attached to the span, + // and using only the modified segmented span prevents the animation from showing. + return replacement; } catch (Exception ex) { LogHelper.printException(() -> "updateRollingNumber failure", ex); + return original; } - return text; } // diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java index c43d603127..8f74b580aa 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java @@ -4,10 +4,21 @@ public class SpoofAppVersionPatch { + private static final boolean SPOOF_APP_VERSION_ENABLED = SettingsEnum.SPOOF_APP_VERSION.getBoolean(); + private static final String SPOOF_APP_VERSION_TARGET = SettingsEnum.SPOOF_APP_VERSION_TARGET.getString(); + + /** + * Injection point + */ public static String getYouTubeVersionOverride(String version) { - if (SettingsEnum.SPOOF_APP_VERSION.getBoolean()) { - return SettingsEnum.SPOOF_APP_VERSION_TARGET.getString(); + if (SPOOF_APP_VERSION_ENABLED) { + return SPOOF_APP_VERSION_TARGET; } return version; } + + public static boolean isSpoofingToEqualOrLessThan(String version) { + return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) <= 0; + } + } 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 420c24bf4e..42d3715d63 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -17,6 +17,7 @@ import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; +import android.text.style.ReplacementSpan; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -84,12 +85,9 @@ public enum Vote { /** * Unique placeholder character, used to detect if a segmented span already has dislikes added to it. - * Ideally is something YouTube is unlikely to use, as it's searched for in all usage of Rolling Number. - * - * Character is not visible as it's overwrote with an drawable span, - * but it should be something narrow as it's horizontal space is retained. + * Must be something YouTube is unlikely to use, as it's searched for in all usage of Rolling Number. */ - private static final char MIDDLE_SEPARATOR_CHARACTER = '⁏'; // 'Reversed Semicolon' + private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye' /** * Cached lookup of all video ids. @@ -118,6 +116,12 @@ public enum Vote { private static final Rect leftSeparatorBounds; private static final Rect middleSeparatorBounds; + /** + * Left separator horizontal padding for Rolling Number layout. + */ + public static final int leftSeparatorShapePaddingPixels; + private static final ShapeDrawable leftSeparatorShape; + static { DisplayMetrics dp = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getDisplayMetrics(); @@ -127,6 +131,11 @@ public enum Vote { final int middleSeparatorSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp); middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize); + + leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0f, dp); + + leftSeparatorShape = new ShapeDrawable(new RectShape()); + leftSeparatorShape.setBounds(leftSeparatorBounds); } private final String videoId; @@ -170,19 +179,31 @@ public enum Vote { @GuardedBy("this") private SpannableString replacementLikeDislikeSpan; + private static int getSeparatorColor() { + return ThemeHelper.isDarkTheme() + ? 0x29AAAAAA // transparent dark gray + : 0xFFD9D9D9; // light gray + } + + public static ShapeDrawable getLeftSeparatorDrawable() { + leftSeparatorShape.getPaint().setColor(getSeparatorColor()); + return leftSeparatorShape; + } + /** * @param isSegmentedButton If UI is using the segmented single UI component for both like and dislike. */ @NonNull - private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton, @NonNull RYDVoteData voteData) { + private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, + boolean isSegmentedButton, + boolean isRollingNumber, + @NonNull RYDVoteData voteData) { if (!isSegmentedButton) { // Simple replacement of 'dislike' with a number/percentage. return newSpannableWithDislikes(oldSpannable, voteData); } - // Note: Some locales use right to left layout (arabic, hebrew, etc), - // and care must be taken to retain the existing RTL encoding character on the likes string, - // otherwise text will incorrectly show as left to right. + // Note: Some locales use right to left layout (Arabic, Hebrew, etc). // If making changes to this code, change device settings to a RTL language and verify layout is correct. String oldLikesString = oldSpannable.toString(); @@ -205,21 +226,24 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, SpannableStringBuilder builder = new SpannableStringBuilder(); final boolean compactLayout = SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean(); - final int separatorColor = ThemeHelper.isDarkTheme() - ? 0x29AAAAAA // transparent dark gray - : 0xFFD9D9D9; // light gray + final int separatorColor = getSeparatorColor(); if (!compactLayout) { - // left separator String leftSeparatorString = ReVancedUtils.isRightToLeftTextLayout() - ? "\u200F " // u200F = right to left character - : "\u200E "; // u200E = left to right character - Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString); - ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape()); - shapeDrawable.getPaint().setColor(separatorColor); - shapeDrawable.setBounds(leftSeparatorBounds); - leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), 1, 2, - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // drawable cannot overwrite RTL or LTR character + ? "\u200F" // u200F = right to left character + : "\u200E"; // u200E = left to right character + final Spannable leftSeparatorSpan; + if (isRollingNumber) { + leftSeparatorSpan = new SpannableString(leftSeparatorString); + } else { + leftSeparatorString += " "; + leftSeparatorSpan = new SpannableString(leftSeparatorString); + // Styling spans cannot overwrite RTL or LTR character. + leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(getLeftSeparatorDrawable(), false), + 1, 2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + leftSeparatorSpan.setSpan(new FixedWidthEmptySpan(leftSeparatorShapePaddingPixels), + 2, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } builder.append(leftSeparatorSpan); } @@ -235,13 +259,33 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); shapeDrawable.getPaint().setColor(separatorColor); shapeDrawable.setBounds(middleSeparatorBounds); - middleSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), shapeInsertionIndex, shapeInsertionIndex + 1, - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + // Use original text width if using compact layout with Rolling Number, + // as there is no empty padding to allow any layout width differences. + VerticallyCenteredImageSpan verticallyCentered + = new VerticallyCenteredImageSpan(shapeDrawable, isRollingNumber && compactLayout); + middleSeparatorSpan.setSpan(verticallyCentered, + shapeInsertionIndex, shapeInsertionIndex + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); builder.append(middleSeparatorSpan); // dislikes builder.append(newSpannableWithDislikes(oldSpannable, voteData)); + // Add some padding to the right for Rolling Number segmented span. + // Use a remove space span, as the layout uses the measured text width and not the + // actual span width. So adding padding and then removing it while drawing gives some + // extra wiggle room for the left separator drawable (which is not measured for layout width). + if (isRollingNumber && !compactLayout) { + // To test this, set the device system font to the smallest available. + // And try different system fonts and try on an Android 8 or 9 device + // (which has no TextView single line mode), or comment + // out the setSingleLineMode() call in the patch. + // If text clipping still occurs, then increase the number of padding spaces below. + Spannable rightPaddingString = new SpannableString(" "); + rightPaddingString.setSpan(new FixedWidthEmptySpan(0), 0, + rightPaddingString.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + builder.append(rightPaddingString); + } + return new SpannableString(builder); } @@ -434,8 +478,10 @@ public synchronized void setVideoIdIsShort(boolean isShort) { * @return the replacement span containing dislikes, or the original span if RYD is not available. */ @NonNull - public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned original, boolean isSegmentedButton) { - return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, false); + public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned original, + boolean isSegmentedButton, + boolean isRollingNumber) { + return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, isRollingNumber,false); } /** @@ -443,12 +489,13 @@ public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned orig */ @NonNull public synchronized Spanned getDislikeSpanForShort(@NonNull Spanned original) { - return waitForFetchAndUpdateReplacementSpan(original, false, true); + return waitForFetchAndUpdateReplacementSpan(original, false, false, true); } @NonNull private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isSegmentedButton, + boolean isRollingNumber, boolean spanIsForShort) { try { RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH); @@ -502,7 +549,7 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, votingData.updateUsingVote(userVote); } originalDislikeSpan = original; - replacementLikeDislikeSpan = createDislikeSpan(original, isSegmentedButton, votingData); + replacementLikeDislikeSpan = createDislikeSpan(original, isSegmentedButton, isRollingNumber, votingData); LogHelper.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '" + replacementLikeDislikeSpan + "'" + " using video: " + videoId); @@ -572,9 +619,44 @@ public void setUserVote(@NonNull Vote vote) { } } +/** + * Styles a Spannable with an empty fixed width. + */ +class FixedWidthEmptySpan extends ReplacementSpan { + final int fixedWidth; + /** + * @param fixedWith Fixed width in screen pixels. + */ + FixedWidthEmptySpan(int fixedWith) { + this.fixedWidth = fixedWith; + if (fixedWith < 0) throw new IllegalArgumentException(); + } + @Override + public int getSize(@NonNull Paint paint, @NonNull CharSequence text, + int start, int end, @Nullable Paint.FontMetricsInt fontMetrics) { + return fixedWidth; + } + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, + float x, int top, int y, int bottom, @NonNull Paint paint) { + // Nothing to draw. + } +} + +/** + * Vertically centers a Spanned Drawable. + */ class VerticallyCenteredImageSpan extends ImageSpan { - public VerticallyCenteredImageSpan(Drawable drawable) { + final boolean useOriginalWidth; + + /** + * @param useOriginalWidth Use the original layout width of the text this span is applied to, + * and not the bounds of the Drawable. Drawable is always displayed using it's own bounds, + * and this setting only affects the layout width of the entire span. + */ + public VerticallyCenteredImageSpan(Drawable drawable, boolean useOriginalWidth) { super(drawable); + this.useOriginalWidth = useOriginalWidth; } @Override @@ -586,13 +668,17 @@ public int getSize(@NonNull Paint paint, @NonNull CharSequence text, Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt(); final int fontHeight = paintMetrics.descent - paintMetrics.ascent; final int drawHeight = bounds.bottom - bounds.top; + final int halfDrawHeight = drawHeight / 2; final int yCenter = paintMetrics.ascent + fontHeight / 2; - fontMetrics.ascent = yCenter - drawHeight / 2; + fontMetrics.ascent = yCenter - halfDrawHeight; fontMetrics.top = fontMetrics.ascent; - fontMetrics.bottom = yCenter + drawHeight / 2; + fontMetrics.bottom = yCenter + halfDrawHeight; fontMetrics.descent = fontMetrics.bottom; } + if (useOriginalWidth) { + return (int) paint.measureText(text, start, end); + } return bounds.right; } @@ -605,8 +691,13 @@ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, final int fontHeight = paintMetrics.descent - paintMetrics.ascent; final int yCenter = y + paintMetrics.descent - fontHeight / 2; final Rect drawBounds = drawable.getBounds(); + float translateX = x; + if (useOriginalWidth) { + // Horizontally center the drawable in the same space as the original text. + translateX += (paint.measureText(text, start, end) - (drawBounds.right - drawBounds.left)) / 2; + } final int translateY = yCenter - (drawBounds.bottom - drawBounds.top) / 2; - canvas.translate(x, translateY); + canvas.translate(translateX, translateY); drawable.draw(canvas); canvas.restore(); } 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 45fffc2cef..e269c9328b 100644 --- a/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java @@ -13,6 +13,7 @@ import android.preference.SwitchPreference; import app.revanced.integrations.patches.ReturnYouTubeDislikePatch; +import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.integrations.settings.SettingsEnum; @@ -21,8 +22,7 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { private static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER = - SettingsEnum.SPOOF_APP_VERSION.getBoolean() - && SettingsEnum.SPOOF_APP_VERSION_TARGET.getString().compareTo("18.33.40") <= 0; + SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40"); /** * If dislikes are shown on Shorts. From fc2cfa0cc7d1ea9903de98a49f94f4247c11f09f Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:25:35 +0200 Subject: [PATCH 15/21] fix: dark mode separator color --- .../ReturnYouTubeDislike.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) 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 42d3715d63..765f99bc5c 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -181,7 +181,7 @@ public enum Vote { private static int getSeparatorColor() { return ThemeHelper.isDarkTheme() - ? 0x29AAAAAA // transparent dark gray + ? 0x33FFFFFF // transparent dark gray : 0xFFD9D9D9; // light gray } @@ -226,7 +226,6 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, SpannableStringBuilder builder = new SpannableStringBuilder(); final boolean compactLayout = SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean(); - final int separatorColor = getSeparatorColor(); if (!compactLayout) { String leftSeparatorString = ReVancedUtils.isRightToLeftTextLayout() @@ -239,9 +238,11 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, leftSeparatorString += " "; leftSeparatorSpan = new SpannableString(leftSeparatorString); // Styling spans cannot overwrite RTL or LTR character. - leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(getLeftSeparatorDrawable(), false), + leftSeparatorSpan.setSpan( + new VerticallyCenteredImageSpan(getLeftSeparatorDrawable(), false), 1, 2, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - leftSeparatorSpan.setSpan(new FixedWidthEmptySpan(leftSeparatorShapePaddingPixels), + leftSeparatorSpan.setSpan( + new FixedWidthEmptySpan(leftSeparatorShapePaddingPixels), 2, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } builder.append(leftSeparatorSpan); @@ -257,29 +258,30 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, final int shapeInsertionIndex = middleSeparatorString.length() / 2; Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString); ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); - shapeDrawable.getPaint().setColor(separatorColor); + shapeDrawable.getPaint().setColor(getSeparatorColor()); shapeDrawable.setBounds(middleSeparatorBounds); // Use original text width if using compact layout with Rolling Number, // as there is no empty padding to allow any layout width differences. - VerticallyCenteredImageSpan verticallyCentered - = new VerticallyCenteredImageSpan(shapeDrawable, isRollingNumber && compactLayout); - middleSeparatorSpan.setSpan(verticallyCentered, + middleSeparatorSpan.setSpan( + new VerticallyCenteredImageSpan(shapeDrawable, isRollingNumber && compactLayout), shapeInsertionIndex, shapeInsertionIndex + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); builder.append(middleSeparatorSpan); // dislikes builder.append(newSpannableWithDislikes(oldSpannable, voteData)); - // Add some padding to the right for Rolling Number segmented span. - // Use a remove space span, as the layout uses the measured text width and not the + // Add some padding for Rolling Number segmented span. + // Use an empty width span, as the layout uses the measured text width and not the // actual span width. So adding padding and then removing it while drawing gives some - // extra wiggle room for the left separator drawable (which is not measured for layout width). + // extra wiggle room for the left separator drawable (which is not included in layout width). if (isRollingNumber && !compactLayout) { // To test this, set the device system font to the smallest available. // And try different system fonts and try on an Android 8 or 9 device - // (which has no TextView single line mode), or comment - // out the setSingleLineMode() call in the patch. + // (which has no TextView single line mode), + // or comment out the setSingleLineMode() call in the patch. // If text clipping still occurs, then increase the number of padding spaces below. + // Any extra width will be padded around the like/dislike string + // as it's set to center text alignment. Spannable rightPaddingString = new SpannableString(" "); rightPaddingString.setSpan(new FixedWidthEmptySpan(0), 0, rightPaddingString.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); From ea46a0789bc10c67a5001fe46420636a05292023 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 17 Nov 2023 20:02:34 +0200 Subject: [PATCH 16/21] cleanup. single line mode is available for all devices --- .../patches/ReturnYouTubeDislikePatch.java | 18 +++++++----------- .../ReturnYouTubeDislike.java | 3 --- 2 files changed, 7 insertions(+), 14 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 8737030295..18e070f43d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -328,9 +328,7 @@ private static void removeRollingNumberPatchChanges(TextView view) { view.setCompoundDrawables(null, null, null, null); view.setGravity(Gravity.NO_GRAVITY); view.setTextAlignment(View.TEXT_ALIGNMENT_INHERIT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - view.setSingleLine(false); - } + view.setSingleLine(false); } } @@ -371,14 +369,12 @@ public static CharSequence updateRollingNumber(TextView view, CharSequence origi removeRollingNumberPatchChanges(view); } else if (view.getCompoundDrawablePadding() == 0) { // YouTube Rolling Numbers do not use compound drawables or drawable padding. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // Single line mode prevents entire words from being entirely clipped, - // and instead only clips the portion of text that runs off. - // The text should not clip due to the empty end padding, - // but use the feature if available just in case. - // If making changes to anything, comment this out and verify layout is still correct. - view.setSingleLine(true); - } + // + // Single line mode prevents entire words from being entirely clipped, + // and instead only clips the portion of text that runs off. + // The text should not clip due to the empty end padding, + // but use the feature just in case. + view.setSingleLine(true); // Center align to distribute the horizontal padding. view.setGravity(Gravity.CENTER); view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); 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 765f99bc5c..f1ed3d12e3 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -276,9 +276,6 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // extra wiggle room for the left separator drawable (which is not included in layout width). if (isRollingNumber && !compactLayout) { // To test this, set the device system font to the smallest available. - // And try different system fonts and try on an Android 8 or 9 device - // (which has no TextView single line mode), - // or comment out the setSingleLineMode() call in the patch. // If text clipping still occurs, then increase the number of padding spaces below. // Any extra width will be padded around the like/dislike string // as it's set to center text alignment. From e1259d448020c37327560494d2d4731ff7dbba60 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 17 Nov 2023 20:58:04 +0200 Subject: [PATCH 17/21] Increase minimum thread count. With RYD now prefetching, spoof client enabled, and SponsorBlock enabled, the client will always run 3 background thread tasks on each video loads. --- .../java/app/revanced/integrations/utils/ReVancedUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/utils/ReVancedUtils.java b/app/src/main/java/app/revanced/integrations/utils/ReVancedUtils.java index 918d55b7f3..6b94bbd972 100644 --- a/app/src/main/java/app/revanced/integrations/utils/ReVancedUtils.java +++ b/app/src/main/java/app/revanced/integrations/utils/ReVancedUtils.java @@ -92,7 +92,7 @@ public static void hideViewUnderCondition(SettingsEnum condition, View view) { * All tasks run at max thread priority. */ private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor( - 2, // 2 threads always ready to go + 3, // 3 threads always ready to go Integer.MAX_VALUE, 10, // For any threads over the minimum, keep them alive 10 seconds after they go idle TimeUnit.SECONDS, From b698fa199199cbd7a4b34a8ea70c8a77a024c348 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 18 Nov 2023 00:25:15 +0100 Subject: [PATCH 18/21] No need to check if RYD is enabled twice (just like in #onLithoTextLoaded) --- .../patches/ReturnYouTubeDislikePatch.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 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 18e070f43d..bd997072e4 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -304,13 +304,11 @@ private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, public static String onRollingNumberLoaded(@NonNull Object conversionContext, @NonNull String original) { try { - if (SettingsEnum.RYD_ENABLED.getBoolean()) { - CharSequence replacement = onLithoTextLoaded(conversionContext, null, original, true); - if (!replacement.toString().equals(original)) { - rollingNumberSpan = replacement; - return replacement.toString(); - } // Else, the text was not a likes count but instead the view count or something else. - } + CharSequence replacement = onLithoTextLoaded(conversionContext, null, original, true); + if (!replacement.toString().equals(original)) { + rollingNumberSpan = replacement; + return replacement.toString(); + } // Else, the text was not a likes count but instead the view count or something else. } catch (Exception ex) { LogHelper.printException(() -> "onRollingNumberLoaded failure", ex); } From a9ef655991124c798df7719b00a33e795749a811 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 18 Nov 2023 00:25:25 +0100 Subject: [PATCH 19/21] optimize imports --- .../patches/ReturnYouTubeDislikePatch.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 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 bd997072e4..44e5108849 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -1,21 +1,20 @@ package app.revanced.integrations.patches; -import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; - import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.os.Build; -import android.text.Editable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextWatcher; +import android.text.*; import android.view.Gravity; import android.view.View; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; +import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.shared.PlayerType; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -23,12 +22,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; -import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.shared.PlayerType; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; +import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; /** * Handles all interaction of UI patch components. From 7abc751bee35cd1b8fa5349ffcf170ea07fdeada Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 18 Nov 2023 00:25:37 +0100 Subject: [PATCH 20/21] reduce noisy code --- .../integrations/patches/spoof/SpoofAppVersionPatch.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java index 8f74b580aa..a861708fda 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofAppVersionPatch.java @@ -11,9 +11,7 @@ public class SpoofAppVersionPatch { * Injection point */ public static String getYouTubeVersionOverride(String version) { - if (SPOOF_APP_VERSION_ENABLED) { - return SPOOF_APP_VERSION_TARGET; - } + if (SPOOF_APP_VERSION_ENABLED) return SPOOF_APP_VERSION_TARGET; return version; } From 25a639e3674d1118eff2b4d1ae3551dd96812e46 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 18 Nov 2023 00:37:31 +0100 Subject: [PATCH 21/21] Move "add" code to a new method complementing the "remove" method --- .../patches/ReturnYouTubeDislikePatch.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 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 44e5108849..c4e395728a 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -324,6 +324,28 @@ private static void removeRollingNumberPatchChanges(TextView view) { } } + /** + * Add Rolling Number text view modifications. + */ + private static void addRollingNumberPatchChanges(TextView view) { + if (view.getCompoundDrawablePadding() == 0) { + LogHelper.printDebug(() -> "Adding rolling number styling to TextView"); + // YouTube Rolling Numbers do not use compound drawables or drawable padding. + // + // Single line mode prevents entire words from being entirely clipped, + // and instead only clips the portion of text that runs off. + // The text should not clip due to the empty end padding, + // but use the feature just in case. + view.setSingleLine(true); + // Center align to distribute the horizontal padding. + view.setGravity(Gravity.CENTER); + view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + ShapeDrawable shapeDrawable = ReturnYouTubeDislike.getLeftSeparatorDrawable(); + view.setCompoundDrawables(shapeDrawable, null, null, null); + view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels); + } + } + /** * Injection point. */ @@ -353,26 +375,14 @@ public static CharSequence updateRollingNumber(TextView view, CharSequence origi // TextView does not display the tall left separator correctly, // as it goes outside the height bounds and messes up the layout. // Fix this by applying the left separator as a text view compound drawable. - // This create a new issue as the compound drawable is not taken into the + // This creates a new issue as the compound drawable is not taken into the // layout width sizing, but that is fixed in the span itself where it uses a blank // padding string that adds to the layout width but is later ignored during UI drawing. if (SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean()) { // Do not apply any TextView changes, and text should always fit without clipping. removeRollingNumberPatchChanges(view); - } else if (view.getCompoundDrawablePadding() == 0) { - // YouTube Rolling Numbers do not use compound drawables or drawable padding. - // - // Single line mode prevents entire words from being entirely clipped, - // and instead only clips the portion of text that runs off. - // The text should not clip due to the empty end padding, - // but use the feature just in case. - view.setSingleLine(true); - // Center align to distribute the horizontal padding. - view.setGravity(Gravity.CENTER); - view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - ShapeDrawable shapeDrawable = ReturnYouTubeDislike.getLeftSeparatorDrawable(); - view.setCompoundDrawables(shapeDrawable, null, null, null); - view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels); + } else { + addRollingNumberPatchChanges(view); } // Remove any padding set by Rolling Number.