mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-02-01 06:37:32 +01:00
feat(YouTube - Return YouTube Dislike): Support version 18.43.45
and 18.44.41
(#514)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
9a6ec6be8c
commit
a5245b85a8
@ -2,28 +2,27 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class HideBreakingNewsPatch {
|
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.
|
* the same layout components as the breaking news shelf.
|
||||||
*
|
*
|
||||||
* Breaking news does not appear to be present in these older versions anyways.
|
* Breaking news does not appear to be present in these older versions anyways.
|
||||||
*/
|
*/
|
||||||
private static boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory() {
|
private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory =
|
||||||
return SettingsEnum.SPOOF_APP_VERSION.getBoolean()
|
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("17.31.00");
|
||||||
&& SettingsEnum.SPOOF_APP_VERSION_TARGET.getString().compareTo("17.30.35") < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void hideBreakingNews(View view) {
|
public static void hideBreakingNews(View view) {
|
||||||
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()
|
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()
|
||||||
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory()) return;
|
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory) return;
|
||||||
ReVancedUtils.hideViewByLayoutParams(view);
|
ReVancedUtils.hideViewByLayoutParams(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
package app.revanced.integrations.patches;
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.Editable;
|
import android.text.*;
|
||||||
import android.text.Spannable;
|
import android.view.Gravity;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -21,12 +22,7 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch;
|
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
||||||
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.
|
* Handles all interaction of UI patch components.
|
||||||
@ -35,13 +31,14 @@ import app.revanced.integrations.utils.ReVancedUtils;
|
|||||||
* Litho based Shorts player can experience temporarily frozen video playback if the RYD fetch takes too long.
|
* Litho based Shorts player can experience temporarily frozen video playback if the RYD fetch takes too long.
|
||||||
*
|
*
|
||||||
* Temporary work around:
|
* 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:
|
* Permanent fix (yet to be implemented), either of:
|
||||||
* - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes asynchronously.
|
* - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes asynchronously.
|
||||||
* - Find a way to force Litho to rebuild it's component tree
|
* - 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).
|
* (and use that hook to force the shorts dislikes to update after the fetch is completed).
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,12 +72,18 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (!rydEnabled) {
|
if (!rydEnabled) {
|
||||||
// Must remove all values to protect against using stale data
|
// Must remove all values to protect against using stale data
|
||||||
// if the user enables RYD while a video is on screen.
|
// if the user enables RYD while a video is on screen.
|
||||||
currentVideoData = null;
|
clearData();
|
||||||
lastLithoShortsVideoData = null;
|
|
||||||
lithoShortsShouldUseCurrentData = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void clearData() {
|
||||||
|
currentVideoData = null;
|
||||||
|
lastLithoShortsVideoData = 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.
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// 17.x non litho regular video player.
|
// 17.x non litho regular video player.
|
||||||
@ -137,7 +140,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (oldUITextView == null) {
|
if (oldUITextView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false);
|
oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false, false);
|
||||||
if (!oldUIReplacementSpan.equals(oldUITextView.getText())) {
|
if (!oldUIReplacementSpan.equals(oldUITextView.getText())) {
|
||||||
oldUITextView.setText(oldUIReplacementSpan);
|
oldUITextView.setText(oldUIReplacementSpan);
|
||||||
}
|
}
|
||||||
@ -188,55 +191,70 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
|
* For Litho segmented buttons and Litho Shorts player.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
|
||||||
|
@Nullable AtomicReference<CharSequence> textRef,
|
||||||
|
@NonNull CharSequence original) {
|
||||||
|
return onLithoTextLoaded(conversionContext, textRef, original, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Called when a litho text component is initially created,
|
* Called when a litho text component is initially created,
|
||||||
* and also when a Span is later reused again (such as scrolling off/on screen).
|
* and also when a Span is later reused again (such as scrolling off/on screen).
|
||||||
*
|
*
|
||||||
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
|
* 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).
|
* 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.
|
* 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.
|
* 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.
|
* @param original Original char sequence was created or reused by Litho.
|
||||||
* @return The original span (if nothing should change), or a replacement span that contains dislikes.
|
* @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
|
@NonNull
|
||||||
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
|
private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
|
||||||
@NonNull AtomicReference<CharSequence> textRef,
|
@Nullable AtomicReference<CharSequence> textRef,
|
||||||
@NonNull CharSequence original) {
|
@NonNull CharSequence original,
|
||||||
|
boolean isRollingNumber) {
|
||||||
try {
|
try {
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
String conversionContextString = conversionContext.toString();
|
String conversionContextString = conversionContext.toString();
|
||||||
// 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|")) {
|
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
|
||||||
// Regular video
|
// Regular video.
|
||||||
ReturnYouTubeDislike videoData = currentVideoData;
|
ReturnYouTubeDislike videoData = currentVideoData;
|
||||||
if (videoData == null) {
|
if (videoData == null) {
|
||||||
return original; // User enabled RYD while a video was on screen.
|
return original; // User enabled RYD while a video was on screen.
|
||||||
}
|
}
|
||||||
replacement = videoData.getDislikesSpanForRegularVideo((Spannable) original, true);
|
if (!(original instanceof Spanned)) {
|
||||||
// When spoofing between 17.09.xx and 17.30.xx the UI is the old layout but uses litho
|
original = new SpannableString(original);
|
||||||
// and the dislikes is "|dislike_button.eml|"
|
}
|
||||||
// but spoofing to that range gives a broken UI layout so no point checking for that.
|
replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original,
|
||||||
} else if (conversionContextString.contains("|shorts_dislike_button.eml|")) {
|
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 (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) {
|
||||||
// Litho Shorts player.
|
// Litho Shorts player.
|
||||||
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
|
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
// Must clear the current video here, otherwise if the user opens a regular video
|
// 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,
|
// 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.
|
// the original video may show the incorrect dislike value.
|
||||||
currentVideoData = null;
|
clearData();
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
ReturnYouTubeDislike videoData = lastLithoShortsVideoData;
|
ReturnYouTubeDislike videoData = lastLithoShortsVideoData;
|
||||||
if (videoData == null) {
|
if (videoData == null) {
|
||||||
// The Shorts litho video id filter did not detect the video id.
|
// 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");
|
LogHelper.printDebug(() -> "Cannot modify Shorts litho span, data is null");
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
@ -250,12 +268,12 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
LogHelper.printDebug(() -> "Using current video data for litho span");
|
LogHelper.printDebug(() -> "Using current video data for litho span");
|
||||||
}
|
}
|
||||||
replacement = videoData.getDislikeSpanForShort((Spannable) original);
|
replacement = videoData.getDislikeSpanForShort((Spanned) original);
|
||||||
} else {
|
} else {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
textRef.set(replacement);
|
if (textRef != null) textRef.set(replacement);
|
||||||
return replacement;
|
return replacement;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "onLithoTextLoaded failure", ex);
|
LogHelper.printException(() -> "onLithoTextLoaded failure", ex);
|
||||||
@ -263,6 +281,123 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Rolling Number
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
private static volatile CharSequence rollingNumberSpan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String onRollingNumberLoaded(@NonNull Object conversionContext,
|
||||||
|
@NonNull String original) {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
view.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public static CharSequence updateRollingNumber(TextView view, CharSequence original) {
|
||||||
|
try {
|
||||||
|
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 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 {
|
||||||
|
addRollingNumberPatchChanges(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Non litho Shorts player.
|
// Non litho Shorts player.
|
||||||
@ -301,7 +436,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
|
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
// Must clear the data here, in case a new video was loaded while PlayerType
|
// 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).
|
// suggested the video was not a short (can happen when spoofing to an old app version).
|
||||||
currentVideoData = null;
|
clearData();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LogHelper.printDebug(() -> "setShortsDislikes");
|
LogHelper.printDebug(() -> "setShortsDislikes");
|
||||||
@ -405,90 +540,59 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
||||||
*/
|
*/
|
||||||
public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
||||||
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
try {
|
||||||
// and the 'is opening/playing' parameter will be false.
|
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
||||||
// This hook will be called again when the Short is actually opened.
|
// and the 'is opening/playing' parameter will be false.
|
||||||
if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) {
|
// This hook will be called again when the Short is actually opened.
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Uses 'current playing' video id hook. Always called on main thread.
|
* Injection point. Uses 'current playing' video id hook. Always called on main thread.
|
||||||
*/
|
*/
|
||||||
public static void newVideoLoaded(@NonNull String videoId) {
|
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 {
|
try {
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||||
|
Objects.requireNonNull(videoId);
|
||||||
|
|
||||||
PlayerType currentPlayerType = PlayerType.getCurrent();
|
PlayerType currentPlayerType = PlayerType.getCurrent();
|
||||||
final boolean isNoneHiddenOrSlidingMinimized = currentPlayerType.isNoneHiddenOrSlidingMinimized();
|
final boolean isNoneHiddenOrSlidingMinimized = currentPlayerType.isNoneHiddenOrSlidingMinimized();
|
||||||
if (isNoneHiddenOrSlidingMinimized && !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
if (isNoneHiddenOrSlidingMinimized && !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
// Must clear here, otherwise the wrong data can be used for a minimized regular video.
|
// Must clear here, otherwise the wrong data can be used for a minimized regular video.
|
||||||
currentVideoData = null;
|
clearData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isShortsLithoVideoId) {
|
if (videoIdIsSame(currentVideoData, videoId)) {
|
||||||
// Litho Shorts video.
|
return;
|
||||||
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");
|
|
||||||
currentVideoData = null;
|
|
||||||
lastLithoShortsVideoData = null;
|
|
||||||
lithoShortsShouldUseCurrentData = false;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
currentVideoData = data;
|
|
||||||
}
|
}
|
||||||
|
LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType);
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType
|
ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
+ " isShortsLithoHook: " + isShortsLithoVideoId);
|
// 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);
|
||||||
|
}
|
||||||
|
currentVideoData = data;
|
||||||
|
|
||||||
// Current video id hook can be called out of order with the non litho Shorts text view hook.
|
// Current video id hook can be called out of order with the non litho Shorts text view hook.
|
||||||
// Must manually update again here.
|
// Must manually update again here.
|
||||||
if (!isShortsLithoVideoId && isNoneHiddenOrSlidingMinimized) {
|
if (isNoneHiddenOrSlidingMinimized) {
|
||||||
updateOnScreenShortsTextViews(true);
|
updateOnScreenShortsTextViews(true);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -496,6 +600,26 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) {
|
||||||
return (fetch == null && videoId == null)
|
return (fetch == null && videoId == null)
|
||||||
|| (fetch != null && fetch.getVideoId().equals(videoId));
|
|| (fetch != null && fetch.getVideoId().equals(videoId));
|
||||||
|
@ -93,7 +93,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||||||
// Must pass a null id to correctly clear out the current video data.
|
// 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,
|
// 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.
|
// the new incognito Short will show the old prior data.
|
||||||
ReturnYouTubeDislikePatch.newVideoLoaded(matchedVideoId, true);
|
ReturnYouTubeDislikePatch.setLastLithoShortsVideoId(matchedVideoId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -4,10 +4,19 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
|
|
||||||
public class SpoofAppVersionPatch {
|
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) {
|
public static String getYouTubeVersionOverride(String version) {
|
||||||
if (SettingsEnum.SPOOF_APP_VERSION.getBoolean()) {
|
if (SPOOF_APP_VERSION_ENABLED) return SPOOF_APP_VERSION_TARGET;
|
||||||
return SettingsEnum.SPOOF_APP_VERSION_TARGET.getString();
|
|
||||||
}
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isSpoofingToEqualOrLessThan(String version) {
|
||||||
|
return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import android.text.SpannableStringBuilder;
|
|||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
|
import android.text.style.ReplacementSpan;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ public class ReturnYouTubeDislike {
|
|||||||
* Must be less than 5 seconds, as per:
|
* Must be less than 5 seconds, as per:
|
||||||
* https://developer.android.com/topic/performance/vitals/anr
|
* 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.
|
* How long to retain successful RYD fetches.
|
||||||
@ -84,9 +85,9 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
||||||
* Can be any almost any non-visible character.
|
* 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 = '\u2009'; // 'narrow space' character
|
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached lookup of all video ids.
|
* Cached lookup of all video ids.
|
||||||
@ -115,6 +116,12 @@ public class ReturnYouTubeDislike {
|
|||||||
private static final Rect leftSeparatorBounds;
|
private static final Rect leftSeparatorBounds;
|
||||||
private static final Rect middleSeparatorBounds;
|
private static final Rect middleSeparatorBounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left separator horizontal padding for Rolling Number layout.
|
||||||
|
*/
|
||||||
|
public static final int leftSeparatorShapePaddingPixels;
|
||||||
|
private static final ShapeDrawable leftSeparatorShape;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
DisplayMetrics dp = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getDisplayMetrics();
|
DisplayMetrics dp = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getDisplayMetrics();
|
||||||
|
|
||||||
@ -124,6 +131,11 @@ public class ReturnYouTubeDislike {
|
|||||||
final int middleSeparatorSize =
|
final int middleSeparatorSize =
|
||||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
|
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
|
||||||
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
|
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;
|
private final String videoId;
|
||||||
@ -167,19 +179,31 @@ public class ReturnYouTubeDislike {
|
|||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private SpannableString replacementLikeDislikeSpan;
|
private SpannableString replacementLikeDislikeSpan;
|
||||||
|
|
||||||
|
private static int getSeparatorColor() {
|
||||||
|
return ThemeHelper.isDarkTheme()
|
||||||
|
? 0x33FFFFFF // 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.
|
* @param isSegmentedButton If UI is using the segmented single UI component for both like and dislike.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@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) {
|
if (!isSegmentedButton) {
|
||||||
// Simple replacement of 'dislike' with a number/percentage.
|
// Simple replacement of 'dislike' with a number/percentage.
|
||||||
return newSpannableWithDislikes(oldSpannable, voteData);
|
return newSpannableWithDislikes(oldSpannable, voteData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Some locales use right to left layout (arabic, hebrew, etc),
|
// 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.
|
|
||||||
// If making changes to this code, change device settings to a RTL language and verify layout is correct.
|
// If making changes to this code, change device settings to a RTL language and verify layout is correct.
|
||||||
String oldLikesString = oldSpannable.toString();
|
String oldLikesString = oldSpannable.toString();
|
||||||
|
|
||||||
@ -202,21 +226,25 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
final boolean compactLayout = SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean();
|
final boolean compactLayout = SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean();
|
||||||
final int separatorColor = ThemeHelper.isDarkTheme()
|
|
||||||
? 0x29AAAAAA // transparent dark gray
|
|
||||||
: 0xFFD9D9D9; // light gray
|
|
||||||
|
|
||||||
if (!compactLayout) {
|
if (!compactLayout) {
|
||||||
// left separator
|
|
||||||
String leftSeparatorString = ReVancedUtils.isRightToLeftTextLayout()
|
String leftSeparatorString = ReVancedUtils.isRightToLeftTextLayout()
|
||||||
? "\u200F " // u200F = right to left character
|
? "\u200F" // u200F = right to left character
|
||||||
: "\u200E "; // u200E = left to right character
|
: "\u200E"; // u200E = left to right character
|
||||||
Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString);
|
final Spannable leftSeparatorSpan;
|
||||||
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
|
if (isRollingNumber) {
|
||||||
shapeDrawable.getPaint().setColor(separatorColor);
|
leftSeparatorSpan = new SpannableString(leftSeparatorString);
|
||||||
shapeDrawable.setBounds(leftSeparatorBounds);
|
} else {
|
||||||
leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), 1, 2,
|
leftSeparatorString += " ";
|
||||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // drawable cannot overwrite RTL or LTR character
|
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);
|
builder.append(leftSeparatorSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,21 +258,41 @@ public class ReturnYouTubeDislike {
|
|||||||
final int shapeInsertionIndex = middleSeparatorString.length() / 2;
|
final int shapeInsertionIndex = middleSeparatorString.length() / 2;
|
||||||
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
|
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
|
||||||
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
|
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
|
||||||
shapeDrawable.getPaint().setColor(separatorColor);
|
shapeDrawable.getPaint().setColor(getSeparatorColor());
|
||||||
shapeDrawable.setBounds(middleSeparatorBounds);
|
shapeDrawable.setBounds(middleSeparatorBounds);
|
||||||
middleSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), shapeInsertionIndex, shapeInsertionIndex + 1,
|
// Use original text width if using compact layout with Rolling Number,
|
||||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
// as there is no empty padding to allow any layout width differences.
|
||||||
|
middleSeparatorSpan.setSpan(
|
||||||
|
new VerticallyCenteredImageSpan(shapeDrawable, isRollingNumber && compactLayout),
|
||||||
|
shapeInsertionIndex, shapeInsertionIndex + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
builder.append(middleSeparatorSpan);
|
builder.append(middleSeparatorSpan);
|
||||||
|
|
||||||
// dislikes
|
// dislikes
|
||||||
builder.append(newSpannableWithDislikes(oldSpannable, voteData));
|
builder.append(newSpannableWithDislikes(oldSpannable, voteData));
|
||||||
|
|
||||||
|
// 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 included in layout width).
|
||||||
|
if (isRollingNumber && !compactLayout) {
|
||||||
|
// To test this, set the device system font to the smallest available.
|
||||||
|
// 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);
|
||||||
|
builder.append(rightPaddingString);
|
||||||
|
}
|
||||||
|
|
||||||
return new SpannableString(builder);
|
return new SpannableString(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 If the text is likely for a previously created likes/dislikes segmented span.
|
||||||
return span.toString().indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1;
|
*/
|
||||||
|
public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) {
|
||||||
|
return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -429,8 +477,10 @@ public class ReturnYouTubeDislike {
|
|||||||
* @return the replacement span containing dislikes, or the original span if RYD is not available.
|
* @return the replacement span containing dislikes, or the original span if RYD is not available.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned original, boolean isSegmentedButton) {
|
public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned original,
|
||||||
return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, false);
|
boolean isSegmentedButton,
|
||||||
|
boolean isRollingNumber) {
|
||||||
|
return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, isRollingNumber,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -438,12 +488,13 @@ public class ReturnYouTubeDislike {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public synchronized Spanned getDislikeSpanForShort(@NonNull Spanned original) {
|
public synchronized Spanned getDislikeSpanForShort(@NonNull Spanned original) {
|
||||||
return waitForFetchAndUpdateReplacementSpan(original, false, true);
|
return waitForFetchAndUpdateReplacementSpan(original, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original,
|
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original,
|
||||||
boolean isSegmentedButton,
|
boolean isSegmentedButton,
|
||||||
|
boolean isRollingNumber,
|
||||||
boolean spanIsForShort) {
|
boolean spanIsForShort) {
|
||||||
try {
|
try {
|
||||||
RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
|
RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
|
||||||
@ -481,7 +532,7 @@ public class ReturnYouTubeDislike {
|
|||||||
return replacementLikeDislikeSpan;
|
return replacementLikeDislikeSpan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original)) {
|
if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original.toString())) {
|
||||||
// need to recreate using original, as original has prior outdated dislike values
|
// need to recreate using original, as original has prior outdated dislike values
|
||||||
if (originalDislikeSpan == null) {
|
if (originalDislikeSpan == null) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
@ -497,7 +548,7 @@ public class ReturnYouTubeDislike {
|
|||||||
votingData.updateUsingVote(userVote);
|
votingData.updateUsingVote(userVote);
|
||||||
}
|
}
|
||||||
originalDislikeSpan = original;
|
originalDislikeSpan = original;
|
||||||
replacementLikeDislikeSpan = createDislikeSpan(original, isSegmentedButton, votingData);
|
replacementLikeDislikeSpan = createDislikeSpan(original, isSegmentedButton, isRollingNumber, votingData);
|
||||||
LogHelper.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
|
LogHelper.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
|
||||||
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);
|
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);
|
||||||
|
|
||||||
@ -567,9 +618,44 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
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);
|
super(drawable);
|
||||||
|
this.useOriginalWidth = useOriginalWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -581,13 +667,17 @@ class VerticallyCenteredImageSpan extends ImageSpan {
|
|||||||
Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt();
|
Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt();
|
||||||
final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
|
final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
|
||||||
final int drawHeight = bounds.bottom - bounds.top;
|
final int drawHeight = bounds.bottom - bounds.top;
|
||||||
|
final int halfDrawHeight = drawHeight / 2;
|
||||||
final int yCenter = paintMetrics.ascent + fontHeight / 2;
|
final int yCenter = paintMetrics.ascent + fontHeight / 2;
|
||||||
|
|
||||||
fontMetrics.ascent = yCenter - drawHeight / 2;
|
fontMetrics.ascent = yCenter - halfDrawHeight;
|
||||||
fontMetrics.top = fontMetrics.ascent;
|
fontMetrics.top = fontMetrics.ascent;
|
||||||
fontMetrics.bottom = yCenter + drawHeight / 2;
|
fontMetrics.bottom = yCenter + halfDrawHeight;
|
||||||
fontMetrics.descent = fontMetrics.bottom;
|
fontMetrics.descent = fontMetrics.bottom;
|
||||||
}
|
}
|
||||||
|
if (useOriginalWidth) {
|
||||||
|
return (int) paint.measureText(text, start, end);
|
||||||
|
}
|
||||||
return bounds.right;
|
return bounds.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,8 +690,13 @@ class VerticallyCenteredImageSpan extends ImageSpan {
|
|||||||
final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
|
final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
|
||||||
final int yCenter = y + paintMetrics.descent - fontHeight / 2;
|
final int yCenter = y + paintMetrics.descent - fontHeight / 2;
|
||||||
final Rect drawBounds = drawable.getBounds();
|
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;
|
final int translateY = yCenter - (drawBounds.bottom - drawBounds.top) / 2;
|
||||||
canvas.translate(x, translateY);
|
canvas.translate(translateX, translateY);
|
||||||
drawable.draw(canvas);
|
drawable.draw(canvas);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
* {@link #fetchVotes(String)} HTTP read timeout.
|
* {@link #fetchVotes(String)} HTTP read timeout.
|
||||||
* To locally debug and force timeouts, change this to a very small number (ie: 100)
|
* 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.
|
* Default connection and response timeout for voting and registration.
|
||||||
|
@ -13,6 +13,7 @@ import android.preference.PreferenceScreen;
|
|||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.ReturnYouTubeDislikePatch;
|
import app.revanced.integrations.patches.ReturnYouTubeDislikePatch;
|
||||||
|
import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch;
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
@ -21,8 +22,7 @@ import app.revanced.integrations.settings.SharedPrefCategory;
|
|||||||
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
private static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
private static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
||||||
SettingsEnum.SPOOF_APP_VERSION.getBoolean()
|
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40");
|
||||||
&& SettingsEnum.SPOOF_APP_VERSION_TARGET.getString().compareTo("18.33.40") <= 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If dislikes are shown on Shorts.
|
* If dislikes are shown on Shorts.
|
||||||
|
@ -92,7 +92,7 @@ public class ReVancedUtils {
|
|||||||
* All tasks run at max thread priority.
|
* All tasks run at max thread priority.
|
||||||
*/
|
*/
|
||||||
private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor(
|
private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor(
|
||||||
2, // 2 threads always ready to go
|
3, // 3 threads always ready to go
|
||||||
Integer.MAX_VALUE,
|
Integer.MAX_VALUE,
|
||||||
10, // For any threads over the minimum, keep them alive 10 seconds after they go idle
|
10, // For any threads over the minimum, keep them alive 10 seconds after they go idle
|
||||||
TimeUnit.SECONDS,
|
TimeUnit.SECONDS,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user