Skip to content
This repository has been archived by the owner on Oct 26, 2024. It is now read-only.

Commit

Permalink
feat(YouTube - Return YouTube Dislike): Support version 18.43.45 an…
Browse files Browse the repository at this point in the history
…d `18.44.41` (#514)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
  • Loading branch information
oSumAtrIX and LisoUseInAIKyrios authored Nov 17, 2023
1 parent 9a6ec6b commit a5245b8
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -69,7 +70,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.
Expand All @@ -84,9 +85,9 @@ 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.
* 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.
Expand Down Expand Up @@ -115,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();

Expand All @@ -124,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;
Expand Down Expand Up @@ -167,19 +179,31 @@ public enum Vote {
@GuardedBy("this")
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.
*/
@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();

Expand All @@ -202,21 +226,25 @@ 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

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);
}

Expand All @@ -230,21 +258,41 @@ 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);
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.
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 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);
}

// 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;
/**
* @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) >= 0;
}

/**
Expand Down Expand Up @@ -429,21 +477,24 @@ 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);
}

/**
* Called when a Shorts dislike Spannable is created.
*/
@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);
Expand Down Expand Up @@ -481,7 +532,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.
Expand All @@ -497,7 +548,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);

Expand Down Expand Up @@ -567,9 +618,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
Expand All @@ -581,13 +667,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;
}

Expand All @@ -600,8 +690,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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down
Loading

0 comments on commit a5245b8

Please sign in to comment.