chore: Merge branch dev to main (#511)

This commit is contained in:
oSumAtrIX 2023-11-18 05:10:38 +01:00 committed by GitHub
commit d0c659ce11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 813 additions and 304 deletions

View File

@ -1,3 +1,72 @@
# [0.122.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.122.0-dev.3...v0.122.0-dev.4) (2023-11-18)
### Features
* **YouTube - Hide layout components:** Hide description components ([726a251](https://github.com/ReVanced/revanced-integrations/commit/726a2510a5b28da3afb77c7f5fdda87a3bd2c809))
# [0.122.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.122.0-dev.2...v0.122.0-dev.3) (2023-11-17)
### Features
* **YouTube - Return YouTube Dislike:** Support version `18.43.45` and `18.44.41` ([#514](https://github.com/ReVanced/revanced-integrations/issues/514)) ([a5245b8](https://github.com/ReVanced/revanced-integrations/commit/a5245b85a829a86b535cf305cac49d14b709708d))
# [0.122.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.122.0-dev.1...v0.122.0-dev.2) (2023-11-17)
### Bug Fixes
* **YouTube - Disable resuming Shorts on startup:** Adjust patch name ([#516](https://github.com/ReVanced/revanced-integrations/issues/516)) ([8b5d2d1](https://github.com/ReVanced/revanced-integrations/commit/8b5d2d1871c19421eb39ce38a7039c86e5d8d08b))
# [0.122.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.4...v0.122.0-dev.1) (2023-11-12)
### Bug Fixes
* **YouTube - Disable suggested video end screen:** Properly hide it every time the screen appears ([828ff6f](https://github.com/ReVanced/revanced-integrations/commit/828ff6f31e2f15bf50899ab1e403bdc40cc09d07))
* **YouTube - Hide layout components:** Reduce false positives when hiding mix playlists ([5f30100](https://github.com/ReVanced/revanced-integrations/commit/5f30100fd59c1e61c0236bc54cfcd03212994cab))
### Features
* **YouTube:** Add `Enable slide to seek` patch ([b1ce7a7](https://github.com/ReVanced/revanced-integrations/commit/b1ce7a75eba53312d9522c87321ac83cb16d83cf))
* **YouTube:** Add `Remove tracking query parameter` patch ([e84b7b3](https://github.com/ReVanced/revanced-integrations/commit/e84b7b328ea48e86d240d38cf83aa960f87d6902))
### Performance Improvements
* **YouTube - Client spoof:** Reduce timeout to fetch storyboard renderer ([847cce4](https://github.com/ReVanced/revanced-integrations/commit/847cce43f6436c592c680820960f5270f799cb8d))
## [0.121.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.3...v0.121.1-dev.4) (2023-11-11)
### Bug Fixes
* **YouTube - Client spoof:** Fix low resolution precise seeking thumbnails ([#513](https://github.com/ReVanced/revanced-integrations/issues/513)) ([11f97ac](https://github.com/ReVanced/revanced-integrations/commit/11f97ac354344aac3d101f8874e38273da15f9e6))
## [0.121.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.2...v0.121.1-dev.3) (2023-11-10)
### Bug Fixes
* **Remove screenshot restriction:** Improve reliability ([#471](https://github.com/ReVanced/revanced-integrations/issues/471)) ([50933dc](https://github.com/ReVanced/revanced-integrations/commit/50933dc42d4ef77fb45a978e19d306dad5070540))
## [0.121.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.1...v0.121.1-dev.2) (2023-11-07)
### Bug Fixes
* **YouTube - Disable suggested video end screen:** Do not spam click to disable the screen ([4f57d56](https://github.com/ReVanced/revanced-integrations/commit/4f57d560425a40386a014da05fe26bb9c22f090f))
* **YouTube:** Prevent playing touch interaction sound when unintended ([6e414ec](https://github.com/ReVanced/revanced-integrations/commit/6e414ec6c2a40d70f810b0ade1d8c41cadafff8d))
## [0.121.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0...v0.121.1-dev.1) (2023-11-06)
### Bug Fixes
* **YouTube:** Rename `Restore old seekbar thumbnails` and `Restore old quality menu` ([#510](https://github.com/ReVanced/revanced-integrations/issues/510)) ([6b00f90](https://github.com/ReVanced/revanced-integrations/commit/6b00f90fb7561d59de59d76d43fff8c1d057dce6))
# [0.121.0](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0...v0.121.0) (2023-11-04)

View File

@ -5,6 +5,10 @@ import android.view.WindowManager;
public class RemoveScreenshotRestrictionPatch {
public static void addFlags(Window window, int flags) {
window.addFlags(flags & ~WindowManager.LayoutParams.FLAG_SECURE);
}
public static void setFlags(Window window, int flags, int mask) {
window.setFlags(flags & ~WindowManager.LayoutParams.FLAG_SECURE, mask & ~WindowManager.LayoutParams.FLAG_SECURE);
}

View File

@ -0,0 +1,14 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
/** @noinspection unused*/
public class DisableResumingStartupShortsPlayerPatch {
/**
* Injection point.
*/
public static boolean disableResumingStartupShortsPlayer() {
return SettingsEnum.DISABLE_RESUMING_SHORTS_PLAYER.getBoolean();
}
}

View File

@ -1,10 +0,0 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
public class DisableStartupShortsPlayerPatch {
//Used by app.revanced.patches.youtube.layout.startupshortsreset.patch.DisableShortsOnStartupPatch
public static boolean disableStartupShortsPlayer() {
return SettingsEnum.DISABLE_RESUMING_SHORTS_PLAYER.getBoolean();
}
}

View File

@ -14,13 +14,17 @@ public final class DisableSuggestedVideoEndScreenPatch {
public static void closeEndScreen(final ImageView imageView) {
if (!SettingsEnum.DISABLE_SUGGESTED_VIDEO_END_SCREEN.getBoolean()) return;
// Get the view which can be listened to for layout changes.
// Get a parent view which can be listened to for layout changes.
final var parent = imageView.getParent().getParent();
// Prevent adding the listener multiple times.
if (lastView == parent) return;
lastView = (ViewGroup)parent;
lastView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> imageView.performClick());
lastView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
// Disable sound effects to prevent the click sound.
imageView.setSoundEffectsEnabled(false);
imageView.performClick();
});
}
}

View File

@ -1,9 +0,0 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
public final class EnableOldSeekbarThumbnailsPatch {
public static boolean enableOldSeekbarThumbnails() {
return !SettingsEnum.ENABLE_OLD_SEEKBAR_THUMBNAILS.getBoolean();
}
}

View File

@ -2,28 +2,27 @@ package app.revanced.integrations.patches;
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);
}
}

View File

@ -0,0 +1,16 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
public final class RemoveTrackingQueryParameterPatch {
private static final String NEW_TRACKING_PARAMETER_REGEX = ".si=.+";
private static final String OLD_TRACKING_PARAMETER_REGEX = ".feature=.+";
public static String sanitize(String url) {
if (!SettingsEnum.REMOVE_TRACKING_QUERY_PARAMETER.getBoolean()) return url;
return url
.replaceAll(NEW_TRACKING_PARAMETER_REGEX, "")
.replaceAll(OLD_TRACKING_PARAMETER_REGEX, "");
}
}

View File

@ -0,0 +1,10 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
@SuppressWarnings("unused")
public final class RestoreOldSeekbarThumbnailsPatch {
public static boolean useFullscreenSeekbarThumbnails() {
return !SettingsEnum.RESTORE_OLD_SEEKBAR_THUMBNAILS.getBoolean();
}
}

View File

@ -1,19 +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;
@ -21,12 +22,7 @@ import java.util.List;
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.
@ -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.
*
* 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.
* - 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 {
/**
@ -75,12 +72,18 @@ public class ReturnYouTubeDislikePatch {
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;
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.
@ -137,7 +140,7 @@ public class ReturnYouTubeDislikePatch {
if (oldUITextView == null) {
return;
}
oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false);
oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false, false);
if (!oldUIReplacementSpan.equals(oldUITextView.getText())) {
oldUITextView.setText(oldUIReplacementSpan);
}
@ -188,55 +191,70 @@ public class ReturnYouTubeDislikePatch {
/**
* 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,
* 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 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 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.
* @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,
@NonNull AtomicReference<CharSequence> textRef,
@NonNull CharSequence original) {
private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
@Nullable AtomicReference<CharSequence> textRef,
@NonNull CharSequence original,
boolean isRollingNumber) {
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
return original;
}
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|")) {
// 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);
// 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|")) {
if (!(original instanceof Spanned)) {
original = new SpannableString(original);
}
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 (!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
// 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;
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;
}
@ -250,12 +268,12 @@ public class ReturnYouTubeDislikePatch {
}
LogHelper.printDebug(() -> "Using current video data for litho span");
}
replacement = videoData.getDislikeSpanForShort((Spannable) original);
replacement = videoData.getDislikeSpanForShort((Spanned) original);
} else {
return original;
}
textRef.set(replacement);
if (textRef != null) textRef.set(replacement);
return replacement;
} catch (Exception ex) {
LogHelper.printException(() -> "onLithoTextLoaded failure", ex);
@ -263,6 +281,123 @@ public class ReturnYouTubeDislikePatch {
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.
@ -301,7 +436,7 @@ public class ReturnYouTubeDislikePatch {
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");
@ -405,90 +540,59 @@ public class ReturnYouTubeDislikePatch {
* 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;
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);
}
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.
*/
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();
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;
}
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");
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;
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);
}
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) {
@ -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) {
return (fetch == null && videoId == null)
|| (fetch != null && fetch.getVideoId().equals(videoId));

View File

@ -0,0 +1,9 @@
package app.revanced.integrations.patches;
import app.revanced.integrations.settings.SettingsEnum;
public final class SlideToSeekPatch {
public static boolean isSlideToSeekDisabled() {
return !SettingsEnum.SLIDE_TO_SEEK.getBoolean();
}
}

View File

@ -0,0 +1,69 @@
package app.revanced.integrations.patches.components;
import androidx.annotation.Nullable;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.StringTrieSearch;
final class DescriptionComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
public DescriptionComponentsFilter() {
exceptions.addPatterns(
"compact_channel",
"description",
"grid_video",
"inline_expander",
"metadata"
);
final StringFilterGroup chapterSection = new StringFilterGroup(
SettingsEnum.HIDE_CHAPTERS,
"macro_markers_carousel"
);
final StringFilterGroup infoCardsSection = new StringFilterGroup(
SettingsEnum.HIDE_INFO_CARDS_SECTION,
"infocards_section"
);
final StringFilterGroup gameSection = new StringFilterGroup(
SettingsEnum.HIDE_GAME_SECTION,
"gaming_section"
);
final StringFilterGroup musicSection = new StringFilterGroup(
SettingsEnum.HIDE_MUSIC_SECTION,
"music_section",
"video_attributes_section"
);
final StringFilterGroup podcastSection = new StringFilterGroup(
SettingsEnum.HIDE_PODCAST_SECTION,
"playlist_section"
);
final StringFilterGroup transcriptSection = new StringFilterGroup(
SettingsEnum.HIDE_TRANSCIPT_SECTION,
"transcript_section"
);
pathFilterGroupList.addAll(
chapterSection,
infoCardsSection,
gameSection,
musicSection,
podcastSection,
transcriptSection
);
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
if (exceptions.matches(path)) return false;
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
}
}

View File

@ -2,10 +2,8 @@ package app.revanced.integrations.patches.components;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.StringTrieSearch;
@ -13,6 +11,9 @@ import app.revanced.integrations.utils.StringTrieSearch;
@RequiresApi(api = Build.VERSION_CODES.N)
public final class LayoutComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
private static ByteArrayAsStringFilterGroup mixPlaylistsExceptions2;
private final CustomFilterGroup custom;
private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
@ -34,6 +35,16 @@ public final class LayoutComponentsFilter extends Filter {
"library_recent_shelf"
);
mixPlaylistsExceptions.addPatterns(
"V.ED", // Playlist browse id.
"java.lang.ref.WeakReference"
);
mixPlaylistsExceptions2 = new ByteArrayAsStringFilterGroup(
null,
"cell_description_body"
);
custom = new CustomFilterGroup(
SettingsEnum.CUSTOM_FILTER,
SettingsEnum.CUSTOM_FILTER_STRINGS
@ -125,11 +136,6 @@ public final class LayoutComponentsFilter extends Filter {
"quality_sheet_footer"
);
final var chapters = new StringFilterGroup(
SettingsEnum.HIDE_CHAPTERS,
"macro_markers_carousel"
);
final var channelBar = new StringFilterGroup(
SettingsEnum.HIDE_CHANNEL_BAR,
"channel_bar"
@ -215,8 +221,7 @@ public final class LayoutComponentsFilter extends Filter {
this.identifierFilterGroupList.addAll(
graySeparator,
chipsShelf,
chapters
chipsShelf
);
}
@ -238,18 +243,23 @@ public final class LayoutComponentsFilter extends Filter {
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
}
/**
* Injection point.
* Called from a different place then the other filters.
*/
public static boolean filterMixPlaylists(final byte[] bytes) {
final boolean isMixPlaylistFiltered = mixPlaylists.check(bytes).isFiltered();
public static boolean filterMixPlaylists(final Object conversionContext, final byte[] bytes) {
// Prevent playlist items being hidden, if a mix playlist is present in it.
if (mixPlaylistsExceptions.matches(conversionContext.toString()))
return false;
if (isMixPlaylistFiltered)
LogHelper.printDebug(() -> "Filtered mix playlist");
if (!mixPlaylists.check(bytes).isFiltered()) return false;
// Prevent hiding the description of some videos accidentally.
if (mixPlaylistsExceptions2.check(bytes).isFiltered()) return false;
LogHelper.printDebug(() -> "Filtered mix playlist");
return true;
return isMixPlaylistFiltered;
}
public static boolean showWatermark() {

View File

@ -2,7 +2,11 @@ package app.revanced.integrations.patches.components;
import androidx.annotation.Nullable;
// Abuse LithoFilter for CustomPlaybackSpeedPatch.
import app.revanced.integrations.patches.playback.speed.CustomPlaybackSpeedPatch;
/**
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
*/
public final class PlaybackSpeedMenuFilterPatch extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
public static volatile boolean isPlaybackSpeedMenuVisible;

View File

@ -93,7 +93,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
// 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;

View File

@ -2,16 +2,19 @@ package app.revanced.integrations.patches.components;
import androidx.annotation.Nullable;
import app.revanced.integrations.patches.playback.quality.RestoreOldVideoQualityMenuPatch;
import app.revanced.integrations.settings.SettingsEnum;
// Abuse LithoFilter for OldVideoQualityMenuPatch.
/**
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
*/
public final class VideoQualityMenuFilterPatch extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
public static volatile boolean isVideoQualityMenuVisible;
public VideoQualityMenuFilterPatch() {
pathFilterGroupList.addAll(new StringFilterGroup(
SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU,
SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js"
));
}

View File

@ -14,13 +14,14 @@ import app.revanced.integrations.utils.LogHelper;
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
* and a ListView in the old one.
*/
public final class OldVideoQualityMenuPatch {
@SuppressWarnings("unused")
public final class RestoreOldVideoQualityMenuPatch {
/**
* Injection point.
*/
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
if (!SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
if (!SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try {
@ -32,6 +33,7 @@ public final class OldVideoQualityMenuPatch {
View advancedQualityView = ((ViewGroup) recyclerView.getChildAt(0)).getChildAt(3);
if (advancedQualityView != null) {
// Click the "Advanced" quality menu to show the "old" quality menu.
advancedQualityView.setSoundEffectsEnabled(false);
advancedQualityView.performClick();
}
}
@ -45,7 +47,7 @@ public final class OldVideoQualityMenuPatch {
* Injection point. Only used if spoofing to an old app version.
*/
public static void showOldVideoQualityMenu(final ListView listView) {
if (!SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
if (!SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override

View File

@ -4,16 +4,14 @@ import android.preference.ListPreference;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import java.util.Arrays;
import app.revanced.integrations.patches.components.PlaybackSpeedMenuFilterPatch;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
import java.util.Arrays;
public class CustomPlaybackSpeedPatch {
/**
* Maximum playback speed, exclusive value. Custom speeds must be less than this value.
@ -111,7 +109,10 @@ public class CustomPlaybackSpeedPatch {
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
// This only shows in phone layout.
parentView4th.getChildAt(0).performClick();
final var touchInsidedView = parentView4th.getChildAt(0);
touchInsidedView.setSoundEffectsEnabled(false);
touchInsidedView.performClick();
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
parentView3rd.setVisibility(View.GONE);

View File

@ -4,10 +4,19 @@ import app.revanced.integrations.settings.SettingsEnum;
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;
}
}

View File

@ -3,6 +3,10 @@ package app.revanced.integrations.patches.spoof;
import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
import static app.revanced.integrations.utils.ReVancedUtils.containsAny;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import java.util.concurrent.ExecutionException;
@ -51,11 +55,15 @@ public class SpoofSignaturePatch {
private static volatile Future<StoryboardRenderer> rendererFuture;
private static volatile boolean useOriginalStoryboardRenderer;
private static volatile boolean isPlayingShorts;
@Nullable
private static StoryboardRenderer getRenderer() {
if (rendererFuture != null) {
try {
return rendererFuture.get(5000, TimeUnit.MILLISECONDS);
return rendererFuture.get(2000, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
LogHelper.printDebug(() -> "Could not get renderer (get timed out)");
} catch (ExecutionException | InterruptedException ex) {
@ -81,27 +89,38 @@ public class SpoofSignaturePatch {
// Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops)
// For this reason, the player parameters of a clip are usually very long (150~300 characters).
// Clips are 60 seconds or less in length, so no spoofing.
var isClip = parameters.length() > 150;
if (isClip) return parameters;
if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters;
// Shorts do not need to be spoofed.
if (parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) return parameters;
if (useOriginalStoryboardRenderer = parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) {
isPlayingShorts = true;
return parameters;
}
isPlayingShorts = false;
boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL && containsAny(parameters, AUTOPLAY_PARAMETERS);
if (isPlayingFeed) return SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean() ?
// Prepend the scrim parameter to mute videos in feed.
SCRIM_PARAMETER + INCOGNITO_PARAMETERS :
// In order to prevent videos that are auto-played in feed to be added to history,
// only spoof the parameter if the video is not playing in the feed.
// This will cause playback issues in the feed, but it's better than manipulating the history.
parameters;
boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL
&& containsAny(parameters, AUTOPLAY_PARAMETERS);
if (isPlayingFeed) {
if (useOriginalStoryboardRenderer = !SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean()) {
// Don't spoof the feed video playback. This will cause video playback issues,
// but only if user continues watching for more than 1 minute.
return parameters;
}
// Spoof the feed video. Video will show up in watch history and video subtitles are missing.
fetchStoryboardRenderer();
return SCRIM_PARAMETER + INCOGNITO_PARAMETERS;
}
fetchStoryboardRenderer();
return INCOGNITO_PARAMETERS;
}
private static void fetchStoryboardRenderer() {
if (!SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) {
lastPlayerResponseVideoId = null;
rendererFuture = null;
return;
}
String videoId = VideoInformation.getPlayerResponseVideoId();
if (!videoId.equals(lastPlayerResponseVideoId)) {
rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
@ -115,11 +134,17 @@ public class SpoofSignaturePatch {
getRenderer();
}
/**
* Injection point.
*/
public static boolean getSeekbarThumbnailOverrideValue() {
return SettingsEnum.SPOOF_SIGNATURE.getBoolean();
private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec,
boolean returnNullIfLiveStream) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
StoryboardRenderer renderer = getRenderer();
if (renderer != null) {
if (returnNullIfLiveStream && renderer.isLiveStream()) return null;
return renderer.getSpec();
}
}
return originalStoryboardRendererSpec;
}
/**
@ -128,19 +153,24 @@ public class SpoofSignaturePatch {
*/
@Nullable
public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
StoryboardRenderer renderer = getRenderer();
if (renderer != null) return renderer.getSpec();
}
return getStoryboardRendererSpec(originalStoryboardRendererSpec, false);
}
return originalStoryboardRendererSpec;
/**
* Injection point.
* Uses additional check to handle live streams.
* Called from background threads and from the main thread.
*/
@Nullable
public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) {
return getStoryboardRendererSpec(originalStoryboardRendererSpec, true);
}
/**
* Injection point.
*/
public static int getRecommendedLevel(int originalLevel) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
StoryboardRenderer renderer = getRenderer();
if (renderer != null) {
Integer recommendedLevel = renderer.getRecommendedLevel();
@ -150,4 +180,30 @@ public class SpoofSignaturePatch {
return originalLevel;
}
/**
* Injection point. Forces seekbar to be shown for paid videos or
* if {@link SettingsEnum#SPOOF_STORYBOARD_RENDERER} is not enabled.
*/
public static boolean getSeekbarThumbnailOverrideValue() {
return SettingsEnum.SPOOF_SIGNATURE.getBoolean();
}
/**
* Injection point.
*
* @param view seekbar thumbnail view. Includes both shorts and regular videos.
*/
public static void seekbarImageViewCreated(ImageView view) {
if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()
|| SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) {
return;
}
if (isPlayingShorts) return;
view.setVisibility(View.GONE);
// Also hide the border around the thumbnail (otherwise a 1 pixel wide bordered frame is visible).
ViewGroup parentLayout = (ViewGroup) view.getParent();
parentLayout.setPadding(0, 0, 0, 0);
}
}

View File

@ -7,11 +7,13 @@ import org.jetbrains.annotations.NotNull;
public final class StoryboardRenderer {
private final String spec;
private final boolean isLiveStream;
@Nullable
private final Integer recommendedLevel;
public StoryboardRenderer(String spec, @Nullable Integer recommendedLevel) {
public StoryboardRenderer(String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) {
this.spec = spec;
this.isLiveStream = isLiveStream;
this.recommendedLevel = recommendedLevel;
}
@ -20,6 +22,10 @@ public final class StoryboardRenderer {
return spec;
}
public boolean isLiveStream() {
return isLiveStream;
}
/**
* @return Recommended image quality level, or NULL if no recommendation exists.
*/
@ -32,7 +38,8 @@ public final class StoryboardRenderer {
@Override
public String toString() {
return "StoryboardRenderer{" +
"spec='" + spec + '\'' +
"isLiveStream=" + isLiveStream +
", spec='" + spec + '\'' +
", recommendedLevel=" + recommendedLevel +
'}';
}

View File

@ -22,6 +22,7 @@ public class StoryboardRendererRequester {
@Nullable
private static JSONObject fetchPlayerResponse(@NonNull String requestBody) {
final long startTime = System.currentTimeMillis();
try {
ReVancedUtils.verifyOffMainThread();
Objects.requireNonNull(requestBody);
@ -40,6 +41,8 @@ public class StoryboardRendererRequester {
LogHelper.printException(() -> "API timed out", ex);
} catch (Exception ex) {
LogHelper.printException(() -> "Failed to fetch storyboard URL", ex);
} finally {
LogHelper.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms");
}
return null;
@ -72,14 +75,17 @@ public class StoryboardRendererRequester {
@Nullable
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
try {
LogHelper.printDebug(() -> "Parsing response: " + playerResponse);
final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
final String storyboardsRendererTag = storyboards.has("playerLiveStoryboardSpecRenderer")
final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer");
final String storyboardsRendererTag = isLiveStream
? "playerLiveStoryboardSpecRenderer"
: "playerStoryboardSpecRenderer";
final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag);
StoryboardRenderer renderer = new StoryboardRenderer(
rendererElement.getString("spec"),
isLiveStream,
rendererElement.has("recommendedLevel")
? rendererElement.getInt("recommendedLevel")
: null

View File

@ -17,6 +17,7 @@ import android.text.SpannableStringBuilder;
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;
@ -69,7 +70,7 @@ public class ReturnYouTubeDislike {
* 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.
@ -84,9 +85,9 @@ public class ReturnYouTubeDislike {
/**
* 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.
@ -115,6 +116,12 @@ public class ReturnYouTubeDislike {
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();
@ -124,6 +131,11 @@ public class ReturnYouTubeDislike {
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;
@ -167,19 +179,31 @@ public class ReturnYouTubeDislike {
@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();
@ -202,21 +226,25 @@ public class ReturnYouTubeDislike {
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);
}
@ -230,21 +258,41 @@ public class ReturnYouTubeDislike {
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;
}
/**
@ -429,8 +477,10 @@ public class ReturnYouTubeDislike {
* @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);
}
/**
@ -438,12 +488,13 @@ public class ReturnYouTubeDislike {
*/
@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);
@ -481,7 +532,7 @@ public class ReturnYouTubeDislike {
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.
@ -497,7 +548,7 @@ public class ReturnYouTubeDislike {
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);
@ -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 {
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
@ -581,13 +667,17 @@ class VerticallyCenteredImageSpan extends ImageSpan {
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;
}
@ -600,8 +690,13 @@ class VerticallyCenteredImageSpan extends ImageSpan {
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();
}

View File

@ -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.

View File

@ -32,7 +32,8 @@ public enum SettingsEnum {
// Video
HDR_AUTO_BRIGHTNESS("revanced_hdr_auto_brightness", BOOLEAN, TRUE),
SHOW_OLD_VIDEO_QUALITY_MENU("revanced_show_old_video_quality_menu", BOOLEAN, TRUE),
@Deprecated SHOW_OLD_VIDEO_QUALITY_MENU("revanced_show_old_video_quality_menu", BOOLEAN, TRUE),
RESTORE_OLD_VIDEO_QUALITY_MENU("revanced_restore_old_video_quality_menu", BOOLEAN, TRUE),
REMEMBER_VIDEO_QUALITY_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
VIDEO_QUALITY_DEFAULT_WIFI("revanced_video_quality_default_wifi", INTEGER, -2),
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_video_quality_default_mobile", INTEGER, -2),
@ -44,41 +45,112 @@ public enum SettingsEnum {
// Ads
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
HIDE_HIDE_LATEST_POSTS("revanced_hide_latest_posts_ads", BOOLEAN, TRUE),
HIDE_MERCHANDISE_BANNERS("revanced_hide_merchandise_banners", BOOLEAN, TRUE),
HIDE_PAID_CONTENT("revanced_hide_paid_content_ads", BOOLEAN, TRUE),
HIDE_PRODUCTS_BANNER("revanced_hide_products_banner", BOOLEAN, TRUE),
HIDE_SELF_SPONSOR("revanced_hide_self_sponsor_ads", BOOLEAN, TRUE),
HIDE_VIDEO_ADS("revanced_hide_video_ads", BOOLEAN, TRUE, true),
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
// Layout
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)),
ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)),
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true),
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
DISABLE_SUGGESTED_VIDEO_END_SCREEN("revanced_disable_suggested_video_end_screen", BOOLEAN, TRUE),
GRADIENT_LOADING_SCREEN("revanced_gradient_loading_screen", BOOLEAN, FALSE),
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
HIDE_AUTOPLAY_BUTTON("revanced_hide_autoplay_button", BOOLEAN, TRUE, true),
HIDE_BREAKING_NEWS("revanced_hide_breaking_news", BOOLEAN, TRUE, true),
HIDE_CAPTIONS_BUTTON("revanced_hide_captions_button", BOOLEAN, FALSE),
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
HIDE_EXPANDABLE_CHIP("revanced_hide_expandable_chip", BOOLEAN, TRUE),
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
HIDE_CHIPS_SHELF("revanced_hide_chips_shelf", BOOLEAN, TRUE),
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
HIDE_COMMUNITY_GUIDELINES("revanced_hide_community_guidelines", BOOLEAN, TRUE),
HIDE_COMMUNITY_POSTS("revanced_hide_community_posts", BOOLEAN, FALSE),
HIDE_COMPACT_BANNER("revanced_hide_compact_banner", BOOLEAN, TRUE),
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
HIDE_EMERGENCY_BOX("revanced_hide_emergency_box", BOOLEAN, TRUE),
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
HIDE_EXPANDABLE_CHIP("revanced_hide_expandable_chip", BOOLEAN, TRUE),
HIDE_FEED_SURVEY("revanced_hide_feed_survey", BOOLEAN, TRUE),
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS("revanced_hide_filter_bar_feed_in_related_videos", BOOLEAN, FALSE, true),
HIDE_FILTER_BAR_FEED_IN_SEARCH("revanced_hide_filter_bar_feed_in_search", BOOLEAN, FALSE, true),
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", BOOLEAN, TRUE, true),
HIDE_GRAY_SEPARATOR("revanced_hide_gray_separator", BOOLEAN, TRUE),
HIDE_TIMED_REACTIONS("revanced_hide_timed_reactions", BOOLEAN, TRUE),
HIDE_SEARCH_RESULT_SHELF_HEADER("revanced_hide_search_result_shelf_header", BOOLEAN, FALSE),
HIDE_NOTIFY_ME_BUTTON("revanced_hide_notify_me_button", BOOLEAN, TRUE),
HIDE_JOIN_MEMBERSHIP_BUTTON("revanced_hide_join_membership_button", BOOLEAN, TRUE),
HIDE_HIDE_CHANNEL_GUIDELINES("revanced_hide_channel_guidelines", BOOLEAN, TRUE),
HIDE_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
HIDE_HIDE_INFO_PANELS("revanced_hide_info_panels", BOOLEAN, TRUE),
HIDE_HOME_BUTTON("revanced_hide_home_button", BOOLEAN, FALSE, true),
HIDE_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
HIDE_JOIN_MEMBERSHIP_BUTTON("revanced_hide_join_membership_button", BOOLEAN, TRUE),
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
HIDE_MEDICAL_PANELS("revanced_hide_medical_panels", BOOLEAN, TRUE),
HIDE_MERCHANDISE_BANNERS("revanced_hide_merchandise_banners", BOOLEAN, TRUE),
HIDE_MIX_PLAYLISTS("revanced_hide_mix_playlists", BOOLEAN, TRUE),
HIDE_MOVIES_SECTION("revanced_hide_movies_section", BOOLEAN, TRUE),
HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES("revanced_hide_subscribers_community_guidelines", BOOLEAN, TRUE),
HIDE_PRODUCTS_BANNER("revanced_hide_products_banner", BOOLEAN, TRUE),
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
HIDE_NOTIFY_ME_BUTTON("revanced_hide_notify_me_button", BOOLEAN, TRUE),
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
HIDE_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
HIDE_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
HIDE_MIX_PLAYLISTS("revanced_hide_mix_playlists", BOOLEAN, TRUE),
HIDE_SEARCH_RESULT_SHELF_HEADER("revanced_hide_search_result_shelf_header", BOOLEAN, FALSE),
HIDE_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, true),
HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES("revanced_hide_subscribers_community_guidelines", BOOLEAN, TRUE),
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
HIDE_TIMED_REACTIONS("revanced_hide_timed_reactions", BOOLEAN, TRUE),
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
@Deprecated HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE),
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)),
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
// Description
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
HIDE_INFO_CARDS_SECTION("revanced_hide_info_cards_section", BOOLEAN, TRUE),
HIDE_GAME_SECTION("revanced_hide_game_section", BOOLEAN, TRUE),
HIDE_MUSIC_SECTION("revanced_hide_music_section", BOOLEAN, TRUE),
HIDE_PODCAST_SECTION("revanced_hide_podcast_section", BOOLEAN, TRUE),
HIDE_TRANSCIPT_SECTION("revanced_hide_transcript_section", BOOLEAN, TRUE),
// Shorts
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
HIDE_SHORTS_JOIN_BUTTON("revanced_hide_shorts_join_button", BOOLEAN, TRUE),
HIDE_SHORTS_SUBSCRIBE_BUTTON("revanced_hide_shorts_subscribe_button", BOOLEAN, TRUE),
HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED("revanced_hide_shorts_subscribe_button_paused", BOOLEAN, FALSE),
HIDE_SHORTS_THANKS_BUTTON("revanced_hide_shorts_thanks_button", BOOLEAN, TRUE),
HIDE_SHORTS_COMMENTS_BUTTON("revanced_hide_shorts_comments_button", BOOLEAN, FALSE),
HIDE_SHORTS_REMIX_BUTTON("revanced_hide_shorts_remix_button", BOOLEAN, TRUE),
HIDE_SHORTS_SHARE_BUTTON("revanced_hide_shorts_share_button", BOOLEAN, FALSE),
HIDE_SHORTS_INFO_PANEL("revanced_hide_shorts_info_panel", BOOLEAN, TRUE),
HIDE_SHORTS_SOUND_BUTTON("revanced_hide_shorts_sound_button", BOOLEAN, FALSE),
HIDE_SHORTS_CHANNEL_BAR("revanced_hide_shorts_channel_bar", BOOLEAN, FALSE),
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
// Seekbar
@Deprecated ENABLE_OLD_SEEKBAR_THUMBNAILS("revanced_enable_old_seekbar_thumbnails", BOOLEAN, TRUE),
RESTORE_OLD_SEEKBAR_THUMBNAILS("revanced_restore_old_seekbar_thumbnails", BOOLEAN, TRUE),
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE),
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
// Action buttons
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
@ -92,69 +164,7 @@ public enum SettingsEnum {
HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE),
HIDE_SHOP_BUTTON("revanced_hide_shop_button", BOOLEAN, TRUE),
// Layout
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
HIDE_AUTOPLAY_BUTTON("revanced_hide_autoplay_button", BOOLEAN, TRUE, true),
HIDE_BREAKING_NEWS("revanced_hide_breaking_news", BOOLEAN, TRUE, true),
HIDE_CAPTIONS_BUTTON("revanced_hide_captions_button", BOOLEAN, FALSE),
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
HIDE_CHIPS_SHELF("revanced_hide_chips_shelf", BOOLEAN, TRUE),
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", BOOLEAN, TRUE, true),
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE),
HIDE_HOME_BUTTON("revanced_hide_home_button", BOOLEAN, FALSE, true),
HIDE_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, true),
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE),
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)),
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
GRADIENT_LOADING_SCREEN("revanced_gradient_loading_screen", BOOLEAN, FALSE),
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
HIDE_FILTER_BAR_FEED_IN_SEARCH("revanced_hide_filter_bar_feed_in_search", BOOLEAN, FALSE, true),
HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS("revanced_hide_filter_bar_feed_in_related_videos", BOOLEAN, FALSE, true),
HIDE_SHORTS_JOIN_BUTTON("revanced_hide_shorts_join_button", BOOLEAN, TRUE),
HIDE_SHORTS_SUBSCRIBE_BUTTON("revanced_hide_shorts_subscribe_button", BOOLEAN, TRUE),
HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED("revanced_hide_shorts_subscribe_button_paused", BOOLEAN, FALSE),
HIDE_SHORTS_THANKS_BUTTON("revanced_hide_shorts_thanks_button", BOOLEAN, TRUE),
HIDE_SHORTS_COMMENTS_BUTTON("revanced_hide_shorts_comments_button", BOOLEAN, FALSE),
HIDE_SHORTS_REMIX_BUTTON("revanced_hide_shorts_remix_button", BOOLEAN, TRUE),
HIDE_SHORTS_SHARE_BUTTON("revanced_hide_shorts_share_button", BOOLEAN, FALSE),
HIDE_SHORTS_INFO_PANEL("revanced_hide_shorts_info_panel", BOOLEAN, TRUE),
HIDE_SHORTS_SOUND_BUTTON("revanced_hide_shorts_sound_button", BOOLEAN, FALSE),
HIDE_SHORTS_CHANNEL_BAR("revanced_hide_shorts_channel_bar", BOOLEAN, FALSE),
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
DISABLE_SUGGESTED_VIDEO_END_SCREEN("revanced_disable_suggested_video_end_screen", BOOLEAN, TRUE),
ENABLE_OLD_SEEKBAR_THUMBNAILS("revanced_enable_old_seekbar_thumbnails", BOOLEAN, TRUE),
DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true),
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)),
ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)),
//Player flyout menu items
// Player flyout menu items
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
HIDE_ADDITIONAL_SETTINGS_MENU("revanced_hide_player_flyout_additional_settings", BOOLEAN, FALSE),
HIDE_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", BOOLEAN, FALSE),
@ -172,17 +182,22 @@ public enum SettingsEnum {
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
SLIDE_TO_SEEK("revanced_slide_to_seek", BOOLEAN, FALSE),
@Deprecated DISABLE_FINE_SCRUBBING_GESTURE("revanced_disable_fine_scrubbing_gesture", BOOLEAN, TRUE),
DISABLE_PRECISE_SEEKING_GESTURE("revanced_disable_precise_seeking_gesture", BOOLEAN, TRUE),
DISABLE_FINE_SCRUBBING_GESTURE("revanced_disable_fine_scrubbing_gesture", BOOLEAN, TRUE),
SPOOF_SIGNATURE("revanced_spoof_signature_verification_enabled", BOOLEAN, TRUE, true,
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false,
parents(SPOOF_SIGNATURE)),
SPOOF_STORYBOARD_RENDERER("revanced_spoof_storyboard", BOOLEAN, TRUE, true,
parents(SPOOF_SIGNATURE)),
SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true),
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
REMOVE_TRACKING_QUERY_PARAMETER("revanced_remove_tracking_query_parameter", BOOLEAN, TRUE),
// Swipe controls
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
@ -383,6 +398,8 @@ public enum SettingsEnum {
migrateOldSettingToNew(HIDE_VIDEO_WATERMARK, HIDE_VIDEO_CHANNEL_WATERMARK);
migrateOldSettingToNew(DISABLE_FINE_SCRUBBING_GESTURE, DISABLE_PRECISE_SEEKING_GESTURE);
migrateOldSettingToNew(SHOW_OLD_VIDEO_QUALITY_MENU, RESTORE_OLD_VIDEO_QUALITY_MENU);
migrateOldSettingToNew(ENABLE_OLD_SEEKBAR_THUMBNAILS, RESTORE_OLD_SEEKBAR_THUMBNAILS);
// Do _not_ delete this SB private user id migration property until sometime in 2024.
// This is the only setting that cannot be reconfigured if lost,

View File

@ -13,6 +13,7 @@ import android.preference.PreferenceScreen;
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 @@ import app.revanced.integrations.settings.SharedPrefCategory;
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.

View File

@ -92,7 +92,7 @@ public class ReVancedUtils {
* 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,

View File

@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
android.useAndroidX = true
version = 0.121.0
version = 0.122.0-dev.4