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

feat(YouTube - Return YouTube Dislike): Support version 18.43.45 and 18.44.41 #514

Merged
merged 22 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
070c4f9
feat(YouTube - Return YouTube Dislike): Support version `18.43.45`
oSumAtrIX Nov 13, 2023
4735846
cleanup
LisoUseInAIKyrios Nov 13, 2023
6ecaed5
fix: rolling number missing separators and span styling
LisoUseInAIKyrios Nov 13, 2023
572eddc
perf: reduce timeout to prior value (no need to wait the extra 0.5 se…
LisoUseInAIKyrios Nov 13, 2023
0896b81
cleanup and comments
LisoUseInAIKyrios Nov 13, 2023
5bd6305
refactor: change method name
LisoUseInAIKyrios Nov 13, 2023
89ec3cf
refactor: simplify
LisoUseInAIKyrios Nov 13, 2023
b9489a8
cleanup
LisoUseInAIKyrios Nov 14, 2023
41a5711
fix over padded likes text if an incognito short is opened/closed whi…
LisoUseInAIKyrios Nov 14, 2023
3777855
fix dislikes not showing after opening/closing a Short while a regula…
LisoUseInAIKyrios Nov 14, 2023
63e8dc7
Use a more unique character since this is now searching thru all roll…
LisoUseInAIKyrios Nov 14, 2023
c4aa441
more robust replacing
LisoUseInAIKyrios Nov 14, 2023
69c503d
Revert "more robust replacing"
LisoUseInAIKyrios Nov 14, 2023
559e595
Fix vertical alignment issues.
LisoUseInAIKyrios Nov 15, 2023
fc2cfa0
fix: dark mode separator color
LisoUseInAIKyrios Nov 17, 2023
ab3e55d
Merge branch 'dev' into feat/bump-ryd
LisoUseInAIKyrios Nov 17, 2023
ea46a07
cleanup. single line mode is available for all devices
LisoUseInAIKyrios Nov 17, 2023
e1259d4
Increase minimum thread count. With RYD now prefetching, spoof client…
LisoUseInAIKyrios Nov 17, 2023
b698fa1
No need to check if RYD is enabled twice (just like in #onLithoTextLo…
oSumAtrIX Nov 17, 2023
a9ef655
optimize imports
oSumAtrIX Nov 17, 2023
7abc751
reduce noisy code
oSumAtrIX Nov 17, 2023
25a639e
Move "add" code to a new method complementing the "remove" method
oSumAtrIX Nov 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,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;
}

}
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