feat: Update YouTube & YouTube Music patches (#6571)
This commit is a squash of multiple commits, authored by the individuals referenced below. To see the exact commits by each author, see the unsquashed tree at https://github.com/ReVanced/revanced-patches/pull/6571 or with commit 03940665d27a42ed08992757dfe4534bd8243356. Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de> Co-authored-by: hoodles <207470673+hoo-dles@users.noreply.github.com> Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> Co-authored-by: OxrxL <108184954+oxrxl@users.noreply.github.com>
This commit is contained in:
co-authored by
hoodles
ILoveOpenSourceApplications
LisoUseInAIKyrios
inotia00
OxrxL
parent
db5e0fe587
commit
88d33b847d
+1
-1
@@ -10,6 +10,6 @@ public final class SanitizeSharingLinksPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeUrlString(url);
|
return sanitizer.sanitizeURLString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,6 +10,6 @@ public final class SanitizeSharingLinksPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeUrlString(url);
|
return sanitizer.sanitizeURLString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-11
@@ -5,14 +5,15 @@ import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.music.settings.Settings;
|
import app.revanced.extension.music.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class NavigationBarPatch {
|
public class NavigationBarPatch {
|
||||||
@NonNull
|
|
||||||
private static String lastYTNavigationEnumName = "";
|
private static String lastYTNavigationEnumName = "";
|
||||||
|
|
||||||
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||||
@@ -25,7 +26,7 @@ public class NavigationBarPatch {
|
|||||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
|
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideNavigationButton(@NonNull View view) {
|
public static void hideNavigationButton(View view) {
|
||||||
// Hide entire navigation bar.
|
// Hide entire navigation bar.
|
||||||
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
|
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
|
||||||
hideViewUnderCondition(true, (View) view.getParent());
|
hideViewUnderCondition(true, (View) view.getParent());
|
||||||
@@ -34,7 +35,7 @@ public class NavigationBarPatch {
|
|||||||
|
|
||||||
// Hide navigation buttons based on their type.
|
// Hide navigation buttons based on their type.
|
||||||
for (NavigationButton button : NavigationButton.values()) {
|
for (NavigationButton button : NavigationButton.values()) {
|
||||||
if (button.ytEnumNames.equals(lastYTNavigationEnumName)) {
|
if (button.ytEnumNames.contains(lastYTNavigationEnumName)) {
|
||||||
hideViewUnderCondition(button.hidden, view);
|
hideViewUnderCondition(button.hidden, view);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -43,30 +44,41 @@ public class NavigationBarPatch {
|
|||||||
|
|
||||||
private enum NavigationButton {
|
private enum NavigationButton {
|
||||||
HOME(
|
HOME(
|
||||||
"TAB_HOME",
|
Arrays.asList(
|
||||||
|
"TAB_HOME"
|
||||||
|
),
|
||||||
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
|
||||||
),
|
),
|
||||||
SAMPLES(
|
SAMPLES(
|
||||||
"TAB_SAMPLES",
|
Arrays.asList(
|
||||||
|
"TAB_SAMPLES"
|
||||||
|
),
|
||||||
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
|
||||||
),
|
),
|
||||||
EXPLORE(
|
EXPLORE(
|
||||||
"TAB_EXPLORE",
|
Arrays.asList(
|
||||||
|
"TAB_EXPLORE"
|
||||||
|
),
|
||||||
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
|
||||||
),
|
),
|
||||||
LIBRARY(
|
LIBRARY(
|
||||||
"LIBRARY_MUSIC",
|
Arrays.asList(
|
||||||
|
"LIBRARY_MUSIC",
|
||||||
|
"TAB_BOOKMARK" // YouTube Music 8.24+
|
||||||
|
),
|
||||||
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
|
||||||
),
|
),
|
||||||
UPGRADE(
|
UPGRADE(
|
||||||
"TAB_MUSIC_PREMIUM",
|
Arrays.asList(
|
||||||
|
"TAB_MUSIC_PREMIUM"
|
||||||
|
),
|
||||||
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
private final String ytEnumNames;
|
private final List<String> ytEnumNames;
|
||||||
private final boolean hidden;
|
private final boolean hidden;
|
||||||
|
|
||||||
NavigationButton(@NonNull String ytEnumNames, boolean hidden) {
|
NavigationButton(List<String> ytEnumNames, boolean hidden) {
|
||||||
this.ytEnumNames = ytEnumNames;
|
this.ytEnumNames = ytEnumNames;
|
||||||
this.hidden = hidden;
|
this.hidden = hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
|||||||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
* ReVanced specific logger. Logging is done to standard device log (accessible through ADB),
|
||||||
* and additionally accessible thru {@link LogBufferManager}.
|
* and additionally accessible through {@link LogBufferManager}.
|
||||||
*
|
*
|
||||||
* All methods are thread safe, and are safe to call even
|
* All methods are thread safe, and are safe to call even
|
||||||
* if {@link Utils#getContext()} is not available.
|
* if {@link Utils#getContext()} is not available.
|
||||||
@@ -202,7 +202,7 @@ public class Logger {
|
|||||||
/**
|
/**
|
||||||
* Logs exceptions under the outer class name of the code calling this method.
|
* Logs exceptions under the outer class name of the code calling this method.
|
||||||
* <p>
|
* <p>
|
||||||
* If the calling code is showing it's own error toast,
|
* If the calling code is showing its own error toast,
|
||||||
* instead use {@link #printInfo(LogMessage, Exception)}
|
* instead use {@link #printInfo(LogMessage, Exception)}
|
||||||
*
|
*
|
||||||
* @param message log message
|
* @param message log message
|
||||||
|
|||||||
+2
-2
@@ -70,7 +70,7 @@ public class StringRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a StringRef object that'll not change it's value
|
* Creates a StringRef object that'll not change its value
|
||||||
*
|
*
|
||||||
* @param value value which toString() method returns when invoked on returned object
|
* @param value value which toString() method returns when invoked on returned object
|
||||||
* @return Unique StringRef instance, its value will never change
|
* @return Unique StringRef instance, its value will never change
|
||||||
@@ -102,7 +102,7 @@ public class StringRef {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
if (resources == null || packageName == null) {
|
if (resources == null || packageName == null) {
|
||||||
Context context = Utils.getContext();
|
var context = Utils.getContext();
|
||||||
resources = context.getResources();
|
resources = context.getResources();
|
||||||
packageName = context.getPackageName();
|
packageName = context.getPackageName();
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -106,14 +106,18 @@ public abstract class TrieSearch<T> {
|
|||||||
* Elements not contained can collide with elements the array does contain,
|
* Elements not contained can collide with elements the array does contain,
|
||||||
* so must compare the nodes character value.
|
* so must compare the nodes character value.
|
||||||
*
|
*
|
||||||
* Alternatively this array could be a sorted and densely packed array,
|
/*
|
||||||
* and lookup is done using binary search.
|
* Alternatively, this could be implemented as a sorted, densely packed array
|
||||||
* That would save a small amount of memory because there's no null children entries,
|
* with lookups performed via binary search.
|
||||||
* but would give a worst case search of O(nlog(m)) where n is the number of
|
* This approach would save a small amount of memory by eliminating null
|
||||||
* characters in the searched text and m is the maximum size of the sorted character arrays.
|
* child entries. However, it would result in a worst-case lookup time of
|
||||||
* Using a hash table array always gives O(n) search time.
|
* O(n log m), where:
|
||||||
* The memory usage here is very small (all Litho filters use ~10KB of memory),
|
* - n is the number of characters in the input text, and
|
||||||
* so the more performant hash implementation is chosen.
|
* - m is the maximum size of the sorted character arrays.
|
||||||
|
* In contrast, using a hash-based array guarantees O(n) lookup time.
|
||||||
|
* Given that the total memory usage is already very small (all Litho filters
|
||||||
|
* together use approximately 10KB), the hash-based implementation is preferred
|
||||||
|
* for its superior performance.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private TrieNode<T>[] children;
|
private TrieNode<T>[] children;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its visibility to GONE.
|
* Hide a view by setting its visibility as GONE.
|
||||||
*
|
*
|
||||||
* @param setting The setting to check for hiding the view.
|
* @param setting The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
@@ -218,7 +218,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its visibility to GONE.
|
* Hide a view by setting its visibility as GONE.
|
||||||
*
|
*
|
||||||
* @param condition The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
@@ -288,7 +288,7 @@ public class Utils {
|
|||||||
// Could do a thread sleep, but that will trigger an exception if the thread is interrupted.
|
// Could do a thread sleep, but that will trigger an exception if the thread is interrupted.
|
||||||
meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random()));
|
meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random()));
|
||||||
}
|
}
|
||||||
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work,
|
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time-wasting work,
|
||||||
// leaving an empty loop that hammers on the System.currentTimeMillis native call.
|
// leaving an empty loop that hammers on the System.currentTimeMillis native call.
|
||||||
return meaninglessValue;
|
return meaninglessValue;
|
||||||
}
|
}
|
||||||
@@ -298,10 +298,12 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int indexOfFirstFound(String value, String... targets) {
|
public static int indexOfFirstFound(String value, String... targets) {
|
||||||
for (String string : targets) {
|
if (isNotEmpty(value)) {
|
||||||
if (!string.isEmpty()) {
|
for (String string : targets) {
|
||||||
final int indexOf = value.indexOf(string);
|
if (!string.isEmpty()) {
|
||||||
if (indexOf >= 0) return indexOf;
|
final int indexOf = value.indexOf(string);
|
||||||
|
if (indexOf >= 0) return indexOf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@@ -473,6 +475,10 @@ public class Utils {
|
|||||||
clipboard.setPrimaryClip(clip);
|
clipboard.setPrimaryClip(clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNotEmpty(@Nullable String str) {
|
||||||
|
return str != null && !str.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isTablet() {
|
public static boolean isTablet() {
|
||||||
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
||||||
}
|
}
|
||||||
@@ -481,7 +487,7 @@ public class Utils {
|
|||||||
private static Boolean isRightToLeftTextLayout;
|
private static Boolean isRightToLeftTextLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc).
|
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc.).
|
||||||
* If this should match any ReVanced language override then instead use
|
* If this should match any ReVanced language override then instead use
|
||||||
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
|
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
|
||||||
* This is the default locale of the device, which may differ if
|
* This is the default locale of the device, which may differ if
|
||||||
@@ -495,7 +501,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc).
|
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc.).
|
||||||
*/
|
*/
|
||||||
public static boolean isRightToLeftLocale(Locale locale) {
|
public static boolean isRightToLeftLocale(Locale locale) {
|
||||||
String displayLanguage = locale.getDisplayLanguage();
|
String displayLanguage = locale.getDisplayLanguage();
|
||||||
@@ -524,7 +530,7 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the text contains at least 1 number character,
|
* @return if the text contains at least 1 number character,
|
||||||
* including any unicode numbers such as Arabic.
|
* including any Unicode numbers such as Arabic.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
public static boolean containsNumber(CharSequence text) {
|
public static boolean containsNumber(CharSequence text) {
|
||||||
@@ -1156,7 +1162,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
|
* Uses {@link #adjustColorBrightness(int, float)} depending on if light or dark mode is active.
|
||||||
*/
|
*/
|
||||||
@ColorInt
|
@ColorInt
|
||||||
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
|
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
|
||||||
|
|||||||
+2
-2
@@ -288,8 +288,8 @@ public final class CheckEnvironmentPatch {
|
|||||||
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
||||||
Boolean timeCheckPassed = nearPatchTime.check();
|
Boolean timeCheckPassed = nearPatchTime.check();
|
||||||
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
// Allow installing recently patched apks,
|
// Allow installing recently patched APKs,
|
||||||
// even if the install source is not Manager or ADB.
|
// even if the installation source is not Manager or ADB.
|
||||||
Check.disableForever();
|
Check.disableForever();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+64
-17
@@ -7,6 +7,8 @@ import android.content.pm.PackageManager;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -53,24 +55,35 @@ public class CustomBrandingPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int notificationSmallIcon;
|
@Nullable
|
||||||
|
private static Integer notificationSmallIcon;
|
||||||
|
|
||||||
static {
|
private static int getNotificationSmallIcon() {
|
||||||
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
// Cannot use static initialization block otherwise cyclic references exist
|
||||||
if (branding == BrandingTheme.ORIGINAL) {
|
// between Settings initialization and this class.
|
||||||
notificationSmallIcon = 0;
|
if (notificationSmallIcon == null) {
|
||||||
} else {
|
if (GmsCoreSupport.isPackageNameOriginal()) {
|
||||||
// Original icon is quantum_ic_video_youtube_white_24
|
Logger.printDebug(() -> "App is root mounted. Not overriding small notification icon");
|
||||||
String iconName = "revanced_notification_icon";
|
return notificationSmallIcon = 0;
|
||||||
if (branding == BrandingTheme.CUSTOM) {
|
|
||||||
iconName += "_custom";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
||||||
if (notificationSmallIcon == 0) {
|
if (branding == BrandingTheme.ORIGINAL) {
|
||||||
Logger.printException(() -> "Could not load notification small icon");
|
notificationSmallIcon = 0;
|
||||||
|
} else {
|
||||||
|
// Original icon is quantum_ic_video_youtube_white_24
|
||||||
|
String iconName = "revanced_notification_icon";
|
||||||
|
if (branding == BrandingTheme.CUSTOM) {
|
||||||
|
iconName += "_custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
||||||
|
if (notificationSmallIcon == 0) {
|
||||||
|
Logger.printException(() -> "Could not load notification small icon");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return notificationSmallIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,8 +102,9 @@ public class CustomBrandingPatch {
|
|||||||
*/
|
*/
|
||||||
public static void setNotificationIcon(Notification.Builder builder) {
|
public static void setNotificationIcon(Notification.Builder builder) {
|
||||||
try {
|
try {
|
||||||
if (notificationSmallIcon != 0) {
|
final int smallIcon = getNotificationSmallIcon();
|
||||||
builder.setSmallIcon(notificationSmallIcon)
|
if (smallIcon != 0) {
|
||||||
|
builder.setSmallIcon(smallIcon)
|
||||||
.setColor(Color.TRANSPARENT); // Remove YT red tint.
|
.setColor(Color.TRANSPARENT); // Remove YT red tint.
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -104,8 +118,41 @@ public class CustomBrandingPatch {
|
|||||||
* The total number of app name aliases, including dummy aliases.
|
* The total number of app name aliases, including dummy aliases.
|
||||||
*/
|
*/
|
||||||
private static int numberOfPresetAppNames() {
|
private static int numberOfPresetAppNames() {
|
||||||
// Modified during patching.
|
// Modified during patching, but requires a default if custom branding is excluded.
|
||||||
throw new IllegalStateException();
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* If a custom icon was provided during patching.
|
||||||
|
*/
|
||||||
|
private static boolean userProvidedCustomIcon() {
|
||||||
|
// Modified during patching, but requires a default if custom branding is excluded.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* If a custom name was provided during patching.
|
||||||
|
*/
|
||||||
|
private static boolean userProvidedCustomName() {
|
||||||
|
// Modified during patching, but requires a default if custom branding is excluded..
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDefaultAppNameIndex() {
|
||||||
|
return userProvidedCustomName()
|
||||||
|
? numberOfPresetAppNames()
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BrandingTheme getDefaultIconStyle() {
|
||||||
|
return userProvidedCustomIcon()
|
||||||
|
? BrandingTheme.CUSTOM
|
||||||
|
: BrandingTheme.ORIGINAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+8
-3
@@ -41,12 +41,13 @@ public final class EnableDebuggingPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
||||||
if (LOG_FEATURE_FLAGS && value) {
|
if (LOG_FEATURE_FLAGS && value) {
|
||||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) {
|
Long flagObj = flag;
|
||||||
|
if (DISABLED_FEATURE_FLAGS.contains(flagObj)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
|
if (featureFlags.putIfAbsent(flagObj, TRUE) == null) {
|
||||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,8 @@ public final class EnableDebuggingPatch {
|
|||||||
*/
|
*/
|
||||||
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
||||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
|
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
||||||
|
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
// Align the log outputs to make post processing easier.
|
// Align the log outputs to make post processing easier.
|
||||||
Logger.printDebug(() -> " double feature is enabled: " + flag
|
Logger.printDebug(() -> " double feature is enabled: " + flag
|
||||||
@@ -74,6 +77,8 @@ public final class EnableDebuggingPatch {
|
|||||||
*/
|
*/
|
||||||
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
||||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
|
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
||||||
|
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> " long feature is enabled: " + flag
|
Logger.printDebug(() -> " long feature is enabled: " + flag
|
||||||
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
||||||
|
|||||||
+2
-2
@@ -18,8 +18,8 @@ public final class SanitizeSharingLinksPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitize(String url) {
|
public static String sanitize(String url) {
|
||||||
if (BaseSettings.SANITIZE_SHARED_LINKS.get()) {
|
if (BaseSettings.SANITIZE_SHARING_LINKS.get()) {
|
||||||
url = sanitizer.sanitizeUrlString(url);
|
url = sanitizer.sanitizeURLString(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
|
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
|
||||||
|
|||||||
+60
-20
@@ -13,6 +13,7 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
@@ -24,7 +25,7 @@ import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class CustomFilter extends Filter {
|
public final class CustomFilter extends Filter {
|
||||||
|
|
||||||
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
private static void showInvalidSyntaxToast(String expression) {
|
||||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,12 @@ public final class CustomFilter extends Filter {
|
|||||||
public static final String SYNTAX_STARTS_WITH = "^";
|
public static final String SYNTAX_STARTS_WITH = "^";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional character that separates the path from a proto buffer string pattern.
|
* Optional character that separates the path from an accessibility string pattern.
|
||||||
|
*/
|
||||||
|
public static final String SYNTAX_ACCESSIBILITY_SYMBOL = "#";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional character that separates the path/accessibility from a proto buffer string pattern.
|
||||||
*/
|
*/
|
||||||
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
||||||
|
|
||||||
@@ -51,15 +57,21 @@ public final class CustomFilter extends Filter {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map key is the path including optional special characters (^ and/or $)
|
// Map key is the full path including optional special characters (^, #, $),
|
||||||
|
// and any accessibility pattern, but does not contain any buffer patterns.
|
||||||
Map<String, CustomFilterGroup> result = new HashMap<>();
|
Map<String, CustomFilterGroup> result = new HashMap<>();
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile(
|
Pattern pattern = Pattern.compile(
|
||||||
"(" // map key group
|
"(" // Map key group.
|
||||||
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)" // optional starts with
|
// Optional starts with.
|
||||||
+ "([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*)" // path
|
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)"
|
||||||
+ "(\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E?)" // optional buffer symbol
|
// Path string.
|
||||||
+ ")" // end map key group
|
+ "([^\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + SYNTAX_BUFFER_SYMBOL + "\\E]*)"
|
||||||
+ "(.*)"); // optional buffer string
|
// Optional accessibility string.
|
||||||
|
+ "(?:\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + "\\E([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*))?"
|
||||||
|
// Optional buffer string.
|
||||||
|
+ "(?:\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E(.*))?"
|
||||||
|
+ ")"); // end map key group
|
||||||
|
|
||||||
for (String expression : rawCustomFilterText.split("\n")) {
|
for (String expression : rawCustomFilterText.split("\n")) {
|
||||||
if (expression.isBlank()) continue;
|
if (expression.isBlank()) continue;
|
||||||
@@ -73,10 +85,12 @@ public final class CustomFilter extends Filter {
|
|||||||
final String mapKey = matcher.group(1);
|
final String mapKey = matcher.group(1);
|
||||||
final boolean pathStartsWith = !matcher.group(2).isEmpty();
|
final boolean pathStartsWith = !matcher.group(2).isEmpty();
|
||||||
final String path = matcher.group(3);
|
final String path = matcher.group(3);
|
||||||
final boolean hasBufferSymbol = !matcher.group(4).isEmpty();
|
final String accessibility = matcher.group(4); // null if not present
|
||||||
final String bufferString = matcher.group(5);
|
final String buffer = matcher.group(5); // null if not present
|
||||||
|
|
||||||
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
if (path.isBlank()
|
||||||
|
|| (accessibility != null && accessibility.isEmpty())
|
||||||
|
|| (buffer != null && buffer.isEmpty())) {
|
||||||
showInvalidSyntaxToast(expression);
|
showInvalidSyntaxToast(expression);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -89,8 +103,13 @@ public final class CustomFilter extends Filter {
|
|||||||
group = new CustomFilterGroup(pathStartsWith, path);
|
group = new CustomFilterGroup(pathStartsWith, path);
|
||||||
result.put(mapKey, group);
|
result.put(mapKey, group);
|
||||||
}
|
}
|
||||||
if (hasBufferSymbol) {
|
|
||||||
group.addBufferString(bufferString);
|
if (accessibility != null) {
|
||||||
|
group.addAccessibilityString(accessibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer != null) {
|
||||||
|
group.addBufferString(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +117,22 @@ public final class CustomFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final boolean startsWith;
|
final boolean startsWith;
|
||||||
|
StringTrieSearch accessibilitySearch;
|
||||||
ByteTrieSearch bufferSearch;
|
ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
CustomFilterGroup(boolean startsWith, String path) {
|
||||||
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||||
this.startsWith = startsWith;
|
this.startsWith = startsWith;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addBufferString(@NonNull String bufferString) {
|
void addAccessibilityString(String accessibilityString) {
|
||||||
|
if (accessibilitySearch == null) {
|
||||||
|
accessibilitySearch = new StringTrieSearch();
|
||||||
|
}
|
||||||
|
accessibilitySearch.addPattern(accessibilityString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addBufferString(String bufferString) {
|
||||||
if (bufferSearch == null) {
|
if (bufferSearch == null) {
|
||||||
bufferSearch = new ByteTrieSearch();
|
bufferSearch = new ByteTrieSearch();
|
||||||
}
|
}
|
||||||
@@ -117,6 +144,11 @@ public final class CustomFilter extends Filter {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append("CustomFilterGroup{");
|
builder.append("CustomFilterGroup{");
|
||||||
|
if (accessibilitySearch != null) {
|
||||||
|
builder.append(", accessibility=");
|
||||||
|
builder.append(accessibilitySearch.getPatterns());
|
||||||
|
}
|
||||||
|
|
||||||
builder.append("path=");
|
builder.append("path=");
|
||||||
if (startsWith) builder.append(SYNTAX_STARTS_WITH);
|
if (startsWith) builder.append(SYNTAX_STARTS_WITH);
|
||||||
builder.append(filters[0]);
|
builder.append(filters[0]);
|
||||||
@@ -146,18 +178,26 @@ public final class CustomFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// All callbacks are custom filter groups.
|
// All callbacks are custom filter groups.
|
||||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||||
|
|
||||||
|
// Check path start requirement.
|
||||||
if (custom.startsWith && contentIndex != 0) {
|
if (custom.startsWith && contentIndex != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (custom.bufferSearch == null) {
|
// Check accessibility string if specified.
|
||||||
return true; // No buffer filter, only path filtering.
|
if (custom.accessibilitySearch != null && !custom.accessibilitySearch.matches(accessibility)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return custom.bufferSearch.matches(buffer);
|
// Check buffer if specified.
|
||||||
|
if (custom.bufferSearch != null && !custom.bufferSearch.matches(buffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // All custom filter conditions passed.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-5
@@ -13,9 +13,9 @@ import java.util.List;
|
|||||||
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
||||||
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||||
*
|
*
|
||||||
* To filter {@link FilterContentType#PROTOBUFFER}, first add a callback to
|
* To filter {@link FilterContentType#PROTOBUFFER} or {@link FilterContentType#ACCESSIBILITY}, first add a callback to
|
||||||
* either an identifier or a path.
|
* either an identifier or a path.
|
||||||
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Then inside {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
||||||
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||||
*
|
*
|
||||||
@@ -26,6 +26,7 @@ public abstract class Filter {
|
|||||||
public enum FilterContentType {
|
public enum FilterContentType {
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
PATH,
|
PATH,
|
||||||
|
ACCESSIBILITY,
|
||||||
PROTOBUFFER
|
PROTOBUFFER
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ public abstract class Filter {
|
|||||||
public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* if any of the groups are found.
|
* if any of the groups are found.
|
||||||
*/
|
*/
|
||||||
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
||||||
@@ -49,7 +50,7 @@ public abstract class Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* if any of the groups are found.
|
* if any of the groups are found.
|
||||||
*/
|
*/
|
||||||
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
||||||
@@ -63,12 +64,15 @@ public abstract class Filter {
|
|||||||
* <p>
|
* <p>
|
||||||
* Method is called off the main thread.
|
* Method is called off the main thread.
|
||||||
*
|
*
|
||||||
|
* @param identifier Litho identifier.
|
||||||
|
* @param accessibility Accessibility string, or an empty string if not present for the component.
|
||||||
|
* @param buffer Protocol buffer.
|
||||||
* @param matchedGroup The actual filter that matched.
|
* @param matchedGroup The actual filter that matched.
|
||||||
* @param contentType The type of content matched.
|
* @param contentType The type of content matched.
|
||||||
* @param contentIndex Matched index of the identifier or path.
|
* @param contentIndex Matched index of the identifier or path.
|
||||||
* @return True if the litho component should be filtered out.
|
* @return True if the litho component should be filtered out.
|
||||||
*/
|
*/
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -122,7 +122,7 @@ public abstract class FilterGroup<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If you have more than 1 filter patterns, then all instances of
|
* If you have more than 1 filter patterns, then all instances of
|
||||||
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
* this class should be filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||||
* which uses a prefix tree to give better performance.
|
* which uses a prefix tree to give better performance.
|
||||||
*/
|
*/
|
||||||
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||||
@@ -149,7 +149,7 @@ public abstract class FilterGroup<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int[] createFailurePattern(byte[] pattern) {
|
private static int[] createFailurePattern(byte[] pattern) {
|
||||||
// Computes the failure function using a boot-strapping process,
|
// Computes the failure function using a bootstrapping process,
|
||||||
// where the pattern is matched against itself.
|
// where the pattern is matched against itself.
|
||||||
final int patternLength = pattern.length;
|
final int patternLength = pattern.length;
|
||||||
final int[] failure = new int[patternLength];
|
final int[] failure = new int[patternLength];
|
||||||
|
|||||||
+48
-19
@@ -24,11 +24,14 @@ public final class LithoFilterPatch {
|
|||||||
private static final class LithoFilterParameters {
|
private static final class LithoFilterParameters {
|
||||||
final String identifier;
|
final String identifier;
|
||||||
final String path;
|
final String path;
|
||||||
|
final String accessibility;
|
||||||
final byte[] buffer;
|
final byte[] buffer;
|
||||||
|
|
||||||
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
|
LithoFilterParameters(String lithoIdentifier, String lithoPath,
|
||||||
|
String accessibility, byte[] buffer) {
|
||||||
this.identifier = lithoIdentifier;
|
this.identifier = lithoIdentifier;
|
||||||
this.path = lithoPath;
|
this.path = lithoPath;
|
||||||
|
this.accessibility = accessibility;
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +42,11 @@ public final class LithoFilterPatch {
|
|||||||
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
||||||
builder.append("ID: ");
|
builder.append("ID: ");
|
||||||
builder.append(identifier);
|
builder.append(identifier);
|
||||||
|
if (!accessibility.isEmpty()) {
|
||||||
|
// AccessibilityId and AccessibilityText are pieces of BufferStrings.
|
||||||
|
builder.append(" Accessibility: ");
|
||||||
|
builder.append(accessibility);
|
||||||
|
}
|
||||||
builder.append(" Path: ");
|
builder.append(" Path: ");
|
||||||
builder.append(path);
|
builder.append(path);
|
||||||
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||||
@@ -122,7 +130,7 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* String suffix for components.
|
* String suffix for components.
|
||||||
* Can be any of: ".eml", ".e-b", ".eml-js", "e-js-b"
|
* Can be any of: ".eml", ".eml-fe", ".e-b", ".eml-js", "e-js-b"
|
||||||
*/
|
*/
|
||||||
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
@@ -132,7 +140,7 @@ public final class LithoFilterPatch {
|
|||||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
|
* Because litho filtering is multithreaded and the buffer is passed in from a different injection point,
|
||||||
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
||||||
* Used for 20.21 and lower.
|
* Used for 20.21 and lower.
|
||||||
*/
|
*/
|
||||||
@@ -140,7 +148,7 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier to protocol buffer mapping. Only used for 20.22+.
|
* Identifier to protocol buffer mapping. Only used for 20.22+.
|
||||||
* Thread local is needed because filtering is multi-threaded and each thread can load
|
* Thread local is needed because filtering is multithreaded and each thread can load
|
||||||
* a different component with the same identifier.
|
* a different component with the same identifier.
|
||||||
*/
|
*/
|
||||||
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
|
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
|
||||||
@@ -155,6 +163,7 @@ public final class LithoFilterPatch {
|
|||||||
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
filterUsingCallbacks(identifierSearchTree, filter,
|
filterUsingCallbacks(identifierSearchTree, filter,
|
||||||
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
||||||
@@ -186,16 +195,13 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||||
parameters.path, parameters.buffer, group, type, matchedStartIndex);
|
parameters.accessibility, parameters.path, parameters.buffer,
|
||||||
|
group, type, matchedStartIndex);
|
||||||
|
|
||||||
if (isFiltered && BaseSettings.DEBUG.get()) {
|
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||||
if (type == Filter.FilterContentType.IDENTIFIER) {
|
Logger.printDebug(() -> type == Filter.FilterContentType.IDENTIFIER
|
||||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
? filterSimpleName + " filtered identifier: " + parameters.identifier
|
||||||
+ " identifier: " + parameters.identifier);
|
: filterSimpleName + " filtered path: " + parameters.path);
|
||||||
} else {
|
|
||||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
|
||||||
+ " path: " + parameters.path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isFiltered;
|
return isFiltered;
|
||||||
@@ -212,7 +218,7 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that differs from {@link Character#isDigit(char)}
|
* Helper function that differs from {@link Character#isDigit(char)}
|
||||||
* as this only matches ascii and not unicode numbers.
|
* as this only matches ascii and not Unicode numbers.
|
||||||
*/
|
*/
|
||||||
private static boolean isAsciiNumber(byte character) {
|
private static boolean isAsciiNumber(byte character) {
|
||||||
return '0' <= character && character <= '9';
|
return '0' <= character && character <= '9';
|
||||||
@@ -233,12 +239,19 @@ public final class LithoFilterPatch {
|
|||||||
Logger.printDebug(() -> "New buffer: " + builder);
|
Logger.printDebug(() -> "New buffer: " + builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The identifier always seems to start very close to the buffer start.
|
||||||
|
// Highest identifier start index ever observed is 50, with most around 30 to 40.
|
||||||
|
// The buffer can be very large with up to 200kb has been observed,
|
||||||
|
// so the search is restricted to only the start.
|
||||||
|
final int maxBufferStartIndex = 500; // 10x expected upper bound.
|
||||||
|
|
||||||
// Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of
|
// Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of
|
||||||
// unique characters, but it seems to be slower since the extra overhead of checking the
|
// unique characters, but it seems to be slower since the extra overhead of checking the
|
||||||
// bad character array negates any performance gain of skipping a few extra subsearches.
|
// bad character array negates any performance gain of skipping a few extra subsearches.
|
||||||
int emlIndex = -1;
|
int emlIndex = -1;
|
||||||
final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length;
|
final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length;
|
||||||
for (int i = 0, lastStartIndex = buffer.length - emlStringLength; i <= lastStartIndex; i++) {
|
final int lastBufferIndexToCheckFrom = Math.min(maxBufferStartIndex, buffer.length - emlStringLength);
|
||||||
|
for (int i = 0; i < lastBufferIndexToCheckFrom; i++) {
|
||||||
boolean match = true;
|
boolean match = true;
|
||||||
for (int j = 0; j < emlStringLength; j++) {
|
for (int j = 0; j < emlStringLength; j++) {
|
||||||
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
|
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
|
||||||
@@ -254,6 +267,9 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
if (emlIndex < 0) {
|
if (emlIndex < 0) {
|
||||||
// Buffer is not used for creating a new litho component.
|
// Buffer is not used for creating a new litho component.
|
||||||
|
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
||||||
|
Logger.printDebug(() -> "Could not find eml index");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +306,9 @@ public final class LithoFilterPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (endIndex < 0) {
|
if (endIndex < 0) {
|
||||||
Logger.printException(() -> "Could not find buffer identifier");
|
if (BaseSettings.DEBUG.get()) {
|
||||||
|
Logger.printException(() -> "Debug: Could not find buffer identifier");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +347,8 @@ public final class LithoFilterPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isFiltered(String identifier, StringBuilder pathBuilder) {
|
public static boolean isFiltered(String identifier, @Nullable String accessibilityId,
|
||||||
|
@Nullable String accessibilityText, StringBuilder pathBuilder) {
|
||||||
try {
|
try {
|
||||||
if (identifier.isEmpty() || pathBuilder.length() == 0) {
|
if (identifier.isEmpty() || pathBuilder.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -340,7 +359,7 @@ public final class LithoFilterPatch {
|
|||||||
final int pipeIndex = identifier.indexOf('|');
|
final int pipeIndex = identifier.indexOf('|');
|
||||||
if (pipeIndex >= 0) {
|
if (pipeIndex >= 0) {
|
||||||
// If the identifier contains no pipe, then it's not an ".eml" identifier
|
// If the identifier contains no pipe, then it's not an ".eml" identifier
|
||||||
// and the buffer is not uniquely identified. Typically this only happens
|
// and the buffer is not uniquely identified. Typically, this only happens
|
||||||
// for subcomponents where buffer filtering is not used.
|
// for subcomponents where buffer filtering is not used.
|
||||||
String identifierKey = identifier.substring(0, pipeIndex);
|
String identifierKey = identifier.substring(0, pipeIndex);
|
||||||
|
|
||||||
@@ -357,7 +376,9 @@ public final class LithoFilterPatch {
|
|||||||
// No buffer is found for some components, such as
|
// No buffer is found for some components, such as
|
||||||
// shorts_lockup_cell.eml on channel profiles.
|
// shorts_lockup_cell.eml on channel profiles.
|
||||||
// For now, just ignore this and filter without a buffer.
|
// For now, just ignore this and filter without a buffer.
|
||||||
Logger.printException(() -> "Could not find global buffer for identifier: " + identifier);
|
if (BaseSettings.DEBUG.get()) {
|
||||||
|
Logger.printException(() -> "Debug: Could not find buffer for identifier: " + identifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +393,15 @@ public final class LithoFilterPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String path = pathBuilder.toString();
|
String path = pathBuilder.toString();
|
||||||
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, buffer);
|
|
||||||
|
String accessibility = "";
|
||||||
|
if (accessibilityId != null && !accessibilityId.isBlank()) {
|
||||||
|
accessibility = accessibilityId;
|
||||||
|
}
|
||||||
|
if (accessibilityText != null && !accessibilityText.isBlank()) {
|
||||||
|
accessibility = accessibilityId + '|' + accessibilityText;
|
||||||
|
}
|
||||||
|
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, accessibility, buffer);
|
||||||
Logger.printDebug(() -> "Searching " + parameter);
|
Logger.printDebug(() -> "Searching " + parameter);
|
||||||
|
|
||||||
return identifierSearchTree.matches(identifier, parameter)
|
return identifierSearchTree.matches(identifier, parameter)
|
||||||
|
|||||||
+9
-9
@@ -24,23 +24,23 @@ public class LinkSanitizer {
|
|||||||
: List.of(parametersToRemove);
|
: List.of(parametersToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sanitizeUrlString(String url) {
|
public String sanitizeURLString(String url) {
|
||||||
try {
|
try {
|
||||||
return sanitizeUri(Uri.parse(url)).toString();
|
return sanitizeURI(Uri.parse(url)).toString();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "sanitizeUrlString failure: " + url, ex);
|
Logger.printException(() -> "sanitizeURLString failure: " + url, ex);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri sanitizeUri(Uri uri) {
|
public Uri sanitizeURI(Uri uri) {
|
||||||
try {
|
try {
|
||||||
String scheme = uri.getScheme();
|
String scheme = uri.getScheme();
|
||||||
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
||||||
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
||||||
// Checking !uri.isHierarchical() works for all cases, except if the
|
// Checking !uri.isHierarchical() works for all cases, except if the
|
||||||
// video title starts with / and then it's hierarchical but still an invalid URI.
|
// video title starts with / and then it's hierarchical but still an invalid URI.
|
||||||
Logger.printDebug(() -> "Ignoring uri: " + uri);
|
Logger.printDebug(() -> "Ignoring URI: " + uri);
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +56,12 @@ public class LinkSanitizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri sanitizedUrl = builder.build();
|
Uri sanitizedURL = builder.build();
|
||||||
Logger.printInfo(() -> "Sanitized url: " + uri + " to: " + sanitizedUrl);
|
Logger.printInfo(() -> "Sanitized URL: " + uri + " to: " + sanitizedURL);
|
||||||
|
|
||||||
return sanitizedUrl;
|
return sanitizedURL;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "sanitizeUri failure: " + uri, ex);
|
Logger.printException(() -> "sanitizeURI failure: " + uri, ex);
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -23,8 +23,8 @@ public class Requester {
|
|||||||
public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException {
|
public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException {
|
||||||
String url = apiUrl + route.getCompiledRoute();
|
String url = apiUrl + route.getCompiledRoute();
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
// Request data is in the URL parameters and no body is sent.
|
// This request sends data via URL query parameters. No request body is included.
|
||||||
// The calling code must set a length if using a request body.
|
// If a request body is added, the caller must set the appropriate Content-Length header.
|
||||||
connection.setFixedLengthStreamingMode(0);
|
connection.setFixedLengthStreamingMode(0);
|
||||||
connection.setRequestMethod(route.getMethod().name());
|
connection.setRequestMethod(route.getMethod().name());
|
||||||
String agentString = System.getProperty("http.agent")
|
String agentString = System.getProperty("http.agent")
|
||||||
|
|||||||
+5
-5
@@ -96,15 +96,15 @@ public abstract class BaseActivityHook extends Activity {
|
|||||||
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
|
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
|
||||||
// Replace dummy placeholder toolbar.
|
// Replace dummy placeholder toolbar.
|
||||||
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
||||||
ViewGroup toolBarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
ViewGroup toolbarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
||||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
|
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolbarParent, "revanced_toolbar");
|
||||||
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||||
toolBarParent.removeView(dummyToolbar);
|
toolbarParent.removeView(dummyToolbar);
|
||||||
|
|
||||||
// Sets appropriate system navigation bar color for the activity.
|
// Sets appropriate system navigation bar color for the activity.
|
||||||
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
|
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
|
||||||
|
|
||||||
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
Toolbar toolbar = new Toolbar(toolbarParent.getContext());
|
||||||
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
||||||
toolbar.setNavigationIcon(getNavigationIcon());
|
toolbar.setNavigationIcon(getNavigationIcon());
|
||||||
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
||||||
@@ -121,7 +121,7 @@ public abstract class BaseActivityHook extends Activity {
|
|||||||
|
|
||||||
onPostToolbarSetup(activity, toolbar, fragment);
|
onPostToolbarSetup(activity, toolbar, fragment);
|
||||||
|
|
||||||
toolBarParent.addView(toolbar, 0);
|
toolbarParent.addView(toolbar, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-3
@@ -6,6 +6,7 @@ import static app.revanced.extension.shared.patches.CustomBrandingPatch.Branding
|
|||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.patches.CustomBrandingPatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings shared across multiple apps.
|
* Settings shared across multiple apps.
|
||||||
@@ -48,13 +49,13 @@ public class BaseSettings {
|
|||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||||
|
|
||||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||||
|
|
||||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
|
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", CustomBrandingPatch.getDefaultIconStyle(), true);
|
||||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
|
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", CustomBrandingPatch.getDefaultAppNameIndex(), true);
|
||||||
|
|
||||||
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
*
|
*
|
||||||
* This intentionally is a static method to deter
|
* This intentionally is a static method to deter
|
||||||
* accidental usage when {@link #save(Boolean)} was intnded.
|
* accidental usage when {@link #save(Boolean)} was intended.
|
||||||
*/
|
*/
|
||||||
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||||
setting.value = Objects.requireNonNull(newValue);
|
setting.value = Objects.requireNonNull(newValue);
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param enumName Enum name. Casing does not matter.
|
* @param enumName Enum name. Casing does not matter.
|
||||||
* @return Enum of this type with the same declared name.
|
* @return Enum of this type with the same declared name.
|
||||||
* @throws IllegalArgumentException if the name is not a valid enum of this type.
|
* @throws IllegalArgumentException if the name is not a valid enum of this type.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-56
@@ -274,60 +274,6 @@ public abstract class Setting<T> {
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
|
||||||
*/
|
|
||||||
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
|
|
||||||
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
if (!oldSetting.isSetToDefault()) {
|
|
||||||
Logger.printInfo(() -> "Migrating old setting value: " + oldSetting + " into replacement setting: " + newSetting);
|
|
||||||
newSetting.save(oldSetting.value);
|
|
||||||
oldSetting.resetToDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate an old Setting value previously stored in a different SharedPreference.
|
|
||||||
* <p>
|
|
||||||
* This method will be deleted in the future.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"rawtypes", "NewApi"})
|
|
||||||
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
|
|
||||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
|
||||||
return; // Nothing to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
Object newValue = setting.get();
|
|
||||||
final Object migratedValue;
|
|
||||||
if (setting instanceof BooleanSetting) {
|
|
||||||
migratedValue = oldPrefs.getBoolean(settingKey, (Boolean) newValue);
|
|
||||||
} else if (setting instanceof IntegerSetting) {
|
|
||||||
migratedValue = oldPrefs.getIntegerString(settingKey, (Integer) newValue);
|
|
||||||
} else if (setting instanceof LongSetting) {
|
|
||||||
migratedValue = oldPrefs.getLongString(settingKey, (Long) newValue);
|
|
||||||
} else if (setting instanceof FloatSetting) {
|
|
||||||
migratedValue = oldPrefs.getFloatString(settingKey, (Float) newValue);
|
|
||||||
} else if (setting instanceof StringSetting) {
|
|
||||||
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
|
||||||
} else {
|
|
||||||
Logger.printException(() -> "Unknown setting: " + setting);
|
|
||||||
// Remove otherwise it'll show a toast on every launch.
|
|
||||||
oldPrefs.preferences.edit().remove(settingKey).apply();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
|
||||||
if (migratedValue.equals(newValue)) {
|
|
||||||
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
|
||||||
return; // Old value is already equal to the new setting value.
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
|
||||||
//noinspection unchecked
|
|
||||||
setting.save(migratedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets, but does _not_ persistently save the value.
|
* Sets, but does _not_ persistently save the value.
|
||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
@@ -419,7 +365,7 @@ public abstract class Setting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the currently set value is the same as {@link #defaultValue}
|
* @return if the currently set value is the same as {@link #defaultValue}.
|
||||||
*/
|
*/
|
||||||
public boolean isSetToDefault() {
|
public boolean isSetToDefault() {
|
||||||
return value.equals(defaultValue);
|
return value.equals(defaultValue);
|
||||||
@@ -450,7 +396,7 @@ public abstract class Setting<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param importExportKey The JSON key. The JSONObject parameter will contain data for this key.
|
* @param importExportKey The JSON key. The JSONObject parameter will contain data for this key.
|
||||||
* @return the value stored using the import/export key. Do not set any values in this method.
|
* @return the value stored using the import/export key. Do not set any values in this method.
|
||||||
*/
|
*/
|
||||||
protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException;
|
protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -208,7 +208,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||||
boolean syncSettingValue,
|
boolean syncSettingValue,
|
||||||
boolean applySettingToPreference) {
|
boolean applySettingToPreference) {
|
||||||
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
// Alternatively this could iterate through all Settings and check for any matching Preferences,
|
||||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||||
// the Preferences.
|
// the Preferences.
|
||||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||||
|
|||||||
+5
-4
@@ -72,7 +72,7 @@ public class FeatureFlagsManagerPreference extends Preference {
|
|||||||
*/
|
*/
|
||||||
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
||||||
45386834L, // 'You' tab settings icon.
|
45386834L, // 'You' tab settings icon.
|
||||||
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
|
45532100L // Cairo flag. Turning this off with all other flags causes the settings menu to be a mix of old/new.
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,9 +131,10 @@ public class FeatureFlagsManagerPreference extends Preference {
|
|||||||
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
||||||
|
|
||||||
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
||||||
// String does not need to be localized because it's basically impossible
|
// It's impossible to reach the settings menu without reaching at least one flag.
|
||||||
// to reach the settings menu without encountering at least 1 flag.
|
// So if theres no flags, then that means the user has just enabled debugging
|
||||||
Utils.showToastShort("No feature flags logged yet");
|
// but has not restarted the app yet.
|
||||||
|
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_no_flags"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -36,18 +36,18 @@ public class SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
||||||
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
preferences.edit().putString(key, (value == null ? null : value.toString())).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any preference data type that has the specified key.
|
* Removes any preference data type that has the specified key.
|
||||||
*/
|
*/
|
||||||
public void removeKey(@NonNull String key) {
|
public void removeKey(@NonNull String key) {
|
||||||
preferences.edit().remove(Objects.requireNonNull(key)).apply();
|
preferences.edit().remove(Objects.requireNonNull(key)).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveBoolean(@NonNull String key, boolean value) {
|
public void saveBoolean(@NonNull String key, boolean value) {
|
||||||
preferences.edit().putBoolean(key, value).apply();
|
preferences.edit().putBoolean(key, value).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+9
-9
@@ -9,36 +9,36 @@ import android.util.AttributeSet;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple preference that opens a url when clicked.
|
* Simple preference that opens a URL when clicked.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class UrlLinkPreference extends Preference {
|
public class URLLinkPreference extends Preference {
|
||||||
|
|
||||||
protected String externalUrl;
|
protected String externalURL;
|
||||||
|
|
||||||
{
|
{
|
||||||
setOnPreferenceClickListener(pref -> {
|
setOnPreferenceClickListener(pref -> {
|
||||||
if (externalUrl == null) {
|
if (externalURL == null) {
|
||||||
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
i.setData(Uri.parse(externalUrl));
|
i.setData(Uri.parse(externalURL));
|
||||||
pref.getContext().startActivity(i);
|
pref.getContext().startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
public UrlLinkPreference(Context context, AttributeSet attrs) {
|
public URLLinkPreference(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
public UrlLinkPreference(Context context) {
|
public URLLinkPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -20,7 +20,7 @@ import app.revanced.extension.shared.ResourceType;
|
|||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for search result items, defining common fields and behavior.
|
* Abstract base class for search result items, defining common fields and behavior.
|
||||||
@@ -167,7 +167,7 @@ public abstract class BaseSearchResultItem {
|
|||||||
if (pref instanceof SwitchPreference) return ViewType.SWITCH;
|
if (pref instanceof SwitchPreference) return ViewType.SWITCH;
|
||||||
if (pref instanceof ListPreference) return ViewType.LIST;
|
if (pref instanceof ListPreference) return ViewType.LIST;
|
||||||
if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER;
|
if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER;
|
||||||
if (pref instanceof UrlLinkPreference) return ViewType.URL_LINK;
|
if (pref instanceof URLLinkPreference) return ViewType.URL_LINK;
|
||||||
if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS;
|
if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS;
|
||||||
return ViewType.REGULAR;
|
return ViewType.REGULAR;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -36,7 +36,7 @@ import app.revanced.extension.shared.ResourceType;
|
|||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||||
import app.revanced.extension.shared.ui.ColorDot;
|
import app.revanced.extension.shared.ui.ColorDot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -436,7 +436,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes string for comparison (removes extra characters, spaces etc).
|
* Normalizes string for comparison (removes extra characters, spaces etc.).
|
||||||
*/
|
*/
|
||||||
protected String normalizeString(String input) {
|
protected String normalizeString(String input) {
|
||||||
if (TextUtils.isEmpty(input)) return "";
|
if (TextUtils.isEmpty(input)) return "";
|
||||||
@@ -609,8 +609,8 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
|||||||
boolean hasNavigationCapability(Preference preference) {
|
boolean hasNavigationCapability(Preference preference) {
|
||||||
// PreferenceScreen always allows navigation.
|
// PreferenceScreen always allows navigation.
|
||||||
if (preference instanceof PreferenceScreen) return true;
|
if (preference instanceof PreferenceScreen) return true;
|
||||||
// UrlLinkPreference does not navigate to a new screen, it opens an external URL.
|
// URLLinkPreference does not navigate to a new screen, it opens an external URL.
|
||||||
if (preference instanceof UrlLinkPreference) return false;
|
if (preference instanceof URLLinkPreference) return false;
|
||||||
// Other group types that might have their own screens.
|
// Other group types that might have their own screens.
|
||||||
if (preference instanceof PreferenceGroup) {
|
if (preference instanceof PreferenceGroup) {
|
||||||
// Check if it has its own fragment or intent.
|
// Check if it has its own fragment or intent.
|
||||||
|
|||||||
+2
-2
@@ -31,7 +31,7 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
|||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video streaming data. Fetching is tied to the behavior YT uses,
|
* Video streaming data. Fetching is tied to the behavior YT uses,
|
||||||
* where this class fetches the streams only when YT fetches.
|
* where this class fetches the streams only when YT fetches.
|
||||||
* <p>
|
* <p>
|
||||||
* Effectively the cache expiration of these fetches is the same as the stock app,
|
* Effectively the cache expiration of these fetches is the same as the stock app,
|
||||||
@@ -86,7 +86,7 @@ public class StreamingDataRequest {
|
|||||||
* Cache limit must be greater than the maximum number of videos open at once,
|
* Cache limit must be greater than the maximum number of videos open at once,
|
||||||
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
|
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
|
||||||
* But instead use a much larger value, to handle if a video viewed a while ago
|
* But instead use a much larger value, to handle if a video viewed a while ago
|
||||||
* is somehow still referenced. Each stream is a small array of Strings
|
* is somehow still referenced. Each stream is a small array of Strings
|
||||||
* so memory usage is not a concern.
|
* so memory usage is not a concern.
|
||||||
*/
|
*/
|
||||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||||
|
|||||||
+1
-1
@@ -14,6 +14,6 @@ public final class SanitizeSharingLinksPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeUrlString(url);
|
return sanitizer.sanitizeURLString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory {
|
|||||||
addPreference(new TogglePreference(context,
|
addPreference(new TogglePreference(context,
|
||||||
"Sanitize sharing links",
|
"Sanitize sharing links",
|
||||||
"Remove tracking parameters from shared links.",
|
"Remove tracking parameters from shared links.",
|
||||||
BaseSettings.SANITIZE_SHARED_LINKS
|
BaseSettings.SANITIZE_SHARING_LINKS
|
||||||
));
|
));
|
||||||
|
|
||||||
addPreference(new TogglePreference(context,
|
addPreference(new TogglePreference(context,
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
package app.revanced.extension.tiktok.share;
|
package app.revanced.extension.tiktok.share;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.privacy.LinkSanitizer;
|
import app.revanced.extension.shared.privacy.LinkSanitizer;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ public final class ShareUrlSanitizer {
|
|||||||
* Injection point for setting check.
|
* Injection point for setting check.
|
||||||
*/
|
*/
|
||||||
public static boolean shouldSanitize() {
|
public static boolean shouldSanitize() {
|
||||||
return BaseSettings.SANITIZE_SHARED_LINKS.get();
|
return BaseSettings.SANITIZE_SHARING_LINKS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +23,6 @@ public final class ShareUrlSanitizer {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitizer.sanitizeUrlString(url);
|
return sanitizer.sanitizeURLString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ dependencies {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 23
|
minSdk = 26
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
<manifest/>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
</manifest>
|
||||||
|
|||||||
+88
-88
@@ -36,7 +36,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
* Can show YouTube provided screen captures of beginning/middle/end of the video.
|
* Can show YouTube provided screen captures of beginning/middle/end of the video.
|
||||||
* (ie: sd1.jpg, sd2.jpg, sd3.jpg).
|
* (ie: sd1.jpg, sd2.jpg, sd3.jpg).
|
||||||
* <p>
|
* <p>
|
||||||
* Or can show crowd-sourced thumbnails provided by DeArrow (<a href="http://dearrow.ajay.app">...</a>).
|
* Or can show crowdsourced thumbnails provided by DeArrow (<a href="http://dearrow.ajay.app">...</a>).
|
||||||
* <p>
|
* <p>
|
||||||
* Or can use DeArrow and fall back to screen captures if DeArrow is not available.
|
* Or can use DeArrow and fall back to screen captures if DeArrow is not available.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -135,12 +135,12 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Uri dearrowApiUri;
|
private static final Uri dearrowAPIURI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The scheme and host of {@link #dearrowApiUri}.
|
* The scheme and host of {@link #dearrowAPIURI}.
|
||||||
*/
|
*/
|
||||||
private static final String deArrowApiUrlPrefix;
|
private static final String deArrowAPIURLPrefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long to temporarily turn off DeArrow if it fails for any reason.
|
* How long to temporarily turn off DeArrow if it fails for any reason.
|
||||||
@@ -148,31 +148,31 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
private static final long DEARROW_FAILURE_API_BACKOFF_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes.
|
private static final long DEARROW_FAILURE_API_BACKOFF_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If non zero, then the system time of when DeArrow API calls can resume.
|
* If non-zero, then the system time of when DeArrow API calls can resume.
|
||||||
*/
|
*/
|
||||||
private static volatile long timeToResumeDeArrowAPICalls;
|
private static volatile long timeToResumeDeArrowAPICalls;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
dearrowApiUri = validateSettings();
|
dearrowAPIURI = validateSettings();
|
||||||
final int port = dearrowApiUri.getPort();
|
final int port = dearrowAPIURI.getPort();
|
||||||
String portString = port == -1 ? "" : (":" + port);
|
String portString = port == -1 ? "" : (":" + port);
|
||||||
deArrowApiUrlPrefix = dearrowApiUri.getScheme() + "://" + dearrowApiUri.getHost() + portString + "/";
|
deArrowAPIURLPrefix = dearrowAPIURI.getScheme() + "://" + dearrowAPIURI.getHost() + portString + "/";
|
||||||
Logger.printDebug(() -> "Using DeArrow API address: " + deArrowApiUrlPrefix);
|
Logger.printDebug(() -> "Using DeArrow API address: " + deArrowAPIURLPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix any bad imported data.
|
* Fix any bad imported data.
|
||||||
*/
|
*/
|
||||||
private static Uri validateSettings() {
|
private static Uri validateSettings() {
|
||||||
Uri apiUri = Uri.parse(Settings.ALT_THUMBNAIL_DEARROW_API_URL.get());
|
Uri apiURI = Uri.parse(Settings.ALT_THUMBNAIL_DEARROW_API_URL.get());
|
||||||
// Cannot use unsecured 'http', otherwise the connections fail to start and no callbacks hooks are made.
|
// Cannot use unsecured 'http', otherwise the connections fail to start and no callbacks hooks are made.
|
||||||
String scheme = apiUri.getScheme();
|
String scheme = apiURI.getScheme();
|
||||||
if (scheme == null || scheme.equals("http") || apiUri.getHost() == null) {
|
if (scheme == null || scheme.equals("http") || apiURI.getHost() == null) {
|
||||||
Utils.showToastLong("Invalid DeArrow API URL. Using default");
|
Utils.showToastLong("Invalid DeArrow API URL. Using default");
|
||||||
Settings.ALT_THUMBNAIL_DEARROW_API_URL.resetToDefault();
|
Settings.ALT_THUMBNAIL_DEARROW_API_URL.resetToDefault();
|
||||||
return validateSettings();
|
return validateSettings();
|
||||||
}
|
}
|
||||||
return apiUri;
|
return apiURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ThumbnailOption optionSettingForCurrentNavigation() {
|
private static ThumbnailOption optionSettingForCurrentNavigation() {
|
||||||
@@ -209,16 +209,16 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the alternative thumbnail url using YouTube provided still video captures.
|
* Build the alternative thumbnail URL using YouTube provided still video captures.
|
||||||
*
|
*
|
||||||
* @param decodedUrl Decoded original thumbnail request url.
|
* @param decodedURL Decoded original thumbnail request url.
|
||||||
* @return The alternative thumbnail url, or if not available NULL.
|
* @return The alternative thumbnail URL, or if not available NULL.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String buildYouTubeVideoStillURL(@NonNull DecodedThumbnailUrl decodedUrl,
|
private static String buildYouTubeVideoStillURL(@NonNull DecodedThumbnailURL decodedURL,
|
||||||
@NonNull ThumbnailQuality qualityToUse) {
|
@NonNull ThumbnailQuality qualityToUse) {
|
||||||
String sanitizedReplacement = decodedUrl.createStillsUrl(qualityToUse, false);
|
String sanitizedReplacement = decodedURL.createStillsURL(qualityToUse, false);
|
||||||
if (VerifiedQualities.verifyAltThumbnailExist(decodedUrl.videoId, qualityToUse, sanitizedReplacement)) {
|
if (VerifiedQualities.verifyAltThumbnailExist(decodedURL.videoId, qualityToUse, sanitizedReplacement)) {
|
||||||
return sanitizedReplacement;
|
return sanitizedReplacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,26 +226,26 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the alternative thumbnail url using DeArrow thumbnail cache.
|
* Build the alternative thumbnail URL using DeArrow thumbnail cache.
|
||||||
*
|
*
|
||||||
* @param videoId ID of the video to get a thumbnail of. Can be any video (regular or Short).
|
* @param videoId ID of the video to get a thumbnail of. Can be any video (regular or Short).
|
||||||
* @param fallbackUrl URL to fall back to in case.
|
* @param fallbackURL URL to fall back to in case.
|
||||||
* @return The alternative thumbnail url, without tracking parameters.
|
* @return The alternative thumbnail URL, without tracking parameters.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
private static String buildDeArrowThumbnailURL(String videoId, String fallbackUrl) {
|
private static String buildDeArrowThumbnailURL(String videoId, String fallbackURL) {
|
||||||
// Build thumbnail request url.
|
// Build thumbnail request URL.
|
||||||
// See https://github.com/ajayyy/DeArrowThumbnailCache/blob/29eb4359ebdf823626c79d944a901492d760bbbc/app.py#L29.
|
// See https://github.com/ajayyy/DeArrowThumbnailCache/blob/29eb4359ebdf823626c79d944a901492d760bbbc/app.py#L29.
|
||||||
return dearrowApiUri
|
return dearrowAPIURI
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.appendQueryParameter("videoID", videoId)
|
.appendQueryParameter("videoId", videoId)
|
||||||
.appendQueryParameter("redirectUrl", fallbackUrl)
|
.appendQueryParameter("redirectURL", fallbackURL)
|
||||||
.build()
|
.build()
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean urlIsDeArrow(@NonNull String imageUrl) {
|
private static boolean urlIsDeArrow(@NonNull String imageURL) {
|
||||||
return imageUrl.startsWith(deArrowApiUrlPrefix);
|
return imageURL.startsWith(deArrowAPIURLPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,7 +264,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void handleDeArrowError(@NonNull String url, int statusCode) {
|
private static void handleDeArrowError(@NonNull String url, int statusCode) {
|
||||||
Logger.printDebug(() -> "Encountered DeArrow error. Url: " + url);
|
Logger.printDebug(() -> "Encountered DeArrow error. URL: " + url);
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
if (timeToResumeDeArrowAPICalls < now) {
|
if (timeToResumeDeArrowAPICalls < now) {
|
||||||
timeToResumeDeArrowAPICalls = now + DEARROW_FAILURE_API_BACKOFF_MILLISECONDS;
|
timeToResumeDeArrowAPICalls = now + DEARROW_FAILURE_API_BACKOFF_MILLISECONDS;
|
||||||
@@ -278,63 +278,63 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread and by multiple threads at the same time.
|
* Injection point. Called off the main thread and by multiple threads at the same time.
|
||||||
*
|
*
|
||||||
* @param originalUrl Image url for all url images loaded, including video thumbnails.
|
* @param originalURL Image URL for all URL images loaded, including video thumbnails.
|
||||||
*/
|
*/
|
||||||
public static String overrideImageURL(String originalUrl) {
|
public static String overrideImageURL(String originalURL) {
|
||||||
try {
|
try {
|
||||||
ThumbnailOption option = optionSettingForCurrentNavigation();
|
ThumbnailOption option = optionSettingForCurrentNavigation();
|
||||||
|
|
||||||
if (option == ThumbnailOption.ORIGINAL) {
|
if (option == ThumbnailOption.ORIGINAL) {
|
||||||
return originalUrl;
|
return originalURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
final var decodedUrl = DecodedThumbnailUrl.decodeImageUrl(originalUrl);
|
final var decodedURL = DecodedThumbnailURL.decodeImageURL(originalURL);
|
||||||
if (decodedUrl == null) {
|
if (decodedURL == null) {
|
||||||
return originalUrl; // Not a thumbnail.
|
return originalURL; // Not a thumbnail.
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Original url: " + decodedUrl.sanitizedUrl);
|
Logger.printDebug(() -> "Original URL: " + decodedURL.sanitizedURL);
|
||||||
|
|
||||||
ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedUrl.imageQuality);
|
ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedURL.imageQuality);
|
||||||
if (qualityToUse == null) {
|
if (qualityToUse == null) {
|
||||||
// Thumbnail is a Short or a Storyboard image used for seekbar thumbnails (must not replace these).
|
// Thumbnail is a Short or a Storyboard image used for seekbar thumbnails (must not replace these).
|
||||||
return originalUrl;
|
return originalURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
String sanitizedReplacementUrl;
|
String sanitizedReplacementURL;
|
||||||
final boolean includeTracking;
|
final boolean includeTracking;
|
||||||
if (option.useDeArrow && canUseDeArrowAPI()) {
|
if (option.useDeArrow && canUseDeArrowAPI()) {
|
||||||
includeTracking = false; // Do not include view tracking parameters with API call.
|
includeTracking = false; // Do not include view tracking parameters with API call.
|
||||||
String fallbackUrl = null;
|
String fallbackURL = null;
|
||||||
if (option.useStillImages) {
|
if (option.useStillImages) {
|
||||||
fallbackUrl = buildYouTubeVideoStillURL(decodedUrl, qualityToUse);
|
fallbackURL = buildYouTubeVideoStillURL(decodedURL, qualityToUse);
|
||||||
}
|
}
|
||||||
if (fallbackUrl == null) {
|
if (fallbackURL == null) {
|
||||||
fallbackUrl = decodedUrl.sanitizedUrl;
|
fallbackURL = decodedURL.sanitizedURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedReplacementUrl = buildDeArrowThumbnailURL(decodedUrl.videoId, fallbackUrl);
|
sanitizedReplacementURL = buildDeArrowThumbnailURL(decodedURL.videoId, fallbackURL);
|
||||||
} else if (option.useStillImages) {
|
} else if (option.useStillImages) {
|
||||||
includeTracking = true; // Include view tracking parameters if present.
|
includeTracking = true; // Include view tracking parameters if present.
|
||||||
sanitizedReplacementUrl = buildYouTubeVideoStillURL(decodedUrl, qualityToUse);
|
sanitizedReplacementURL = buildYouTubeVideoStillURL(decodedURL, qualityToUse);
|
||||||
if (sanitizedReplacementUrl == null) {
|
if (sanitizedReplacementURL == null) {
|
||||||
return originalUrl; // Still capture is not available. Return the untouched original url.
|
return originalURL; // Still capture is not available. Return the untouched original url.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return originalUrl; // Recently experienced DeArrow failure and video stills are not enabled.
|
return originalURL; // Recently experienced DeArrow failure and video stills are not enabled.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not log any tracking parameters.
|
// Do not log any tracking parameters.
|
||||||
Logger.printDebug(() -> "Replacement url: " + sanitizedReplacementUrl);
|
Logger.printDebug(() -> "Replacement URL: " + sanitizedReplacementURL);
|
||||||
|
|
||||||
return includeTracking
|
return includeTracking
|
||||||
? sanitizedReplacementUrl + decodedUrl.viewTrackingParameters
|
? sanitizedReplacementURL + decodedURL.viewTrackingParameters
|
||||||
: sanitizedReplacementUrl;
|
: sanitizedReplacementURL;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "overrideImageURL failure", ex);
|
Logger.printException(() -> "overrideImageURL failure", ex);
|
||||||
return originalUrl;
|
return originalURL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,21 +370,21 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
// - very old
|
// - very old
|
||||||
// - very low view count
|
// - very low view count
|
||||||
// Take note of this, so if the image reloads the original thumbnail will be used.
|
// Take note of this, so if the image reloads the original thumbnail will be used.
|
||||||
DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(url);
|
DecodedThumbnailURL decodedURL = DecodedThumbnailURL.decodeImageURL(url);
|
||||||
if (decodedUrl == null) {
|
if (decodedURL == null) {
|
||||||
return; // Not a thumbnail.
|
return; // Not a thumbnail.
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "handleCronetSuccess, image not available: " + decodedUrl.sanitizedUrl);
|
Logger.printDebug(() -> "handleCronetSuccess, image not available: " + decodedURL.sanitizedURL);
|
||||||
|
|
||||||
ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality);
|
ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedURL.imageQuality);
|
||||||
if (quality == null) {
|
if (quality == null) {
|
||||||
// Video is a short or a seekbar thumbnail, but somehow did not load. Should not happen.
|
// Video is a short or a seekbar thumbnail, but somehow did not load. Should not happen.
|
||||||
Logger.printDebug(() -> "Failed to recognize image quality of url: " + decodedUrl.sanitizedUrl);
|
Logger.printDebug(() -> "Failed to recognize image quality of URL: " + decodedURL.sanitizedURL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VerifiedQualities.setAltThumbnailDoesNotExist(decodedUrl.videoId, quality);
|
VerifiedQualities.setAltThumbnailDoesNotExist(decodedURL.videoId, quality);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Callback success error", ex);
|
Logger.printException(() -> "Callback success error", ex);
|
||||||
@@ -482,7 +482,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
// (even though search and subscriptions use the exact same layout as the home feed).
|
// (even though search and subscriptions use the exact same layout as the home feed).
|
||||||
// Of note, this image quality issue only appears with the alt thumbnail images,
|
// Of note, this image quality issue only appears with the alt thumbnail images,
|
||||||
// and the regular thumbnails have identical color/contrast quality for all sizes.
|
// and the regular thumbnails have identical color/contrast quality for all sizes.
|
||||||
// Fix this by falling thru and upgrading SD to 720.
|
// Fix this by falling through and upgrading SD to 720.
|
||||||
case SDDEFAULT, HQ720 -> { // SD is max resolution for fast alt images.
|
case SDDEFAULT, HQ720 -> { // SD is max resolution for fast alt images.
|
||||||
if (useFastQuality) {
|
if (useFastQuality) {
|
||||||
yield SDDEFAULT;
|
yield SDDEFAULT;
|
||||||
@@ -525,7 +525,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
private static final long NOT_AVAILABLE_TIMEOUT_MILLISECONDS = 10 * 60 * 1000; // 10 minutes.
|
private static final long NOT_AVAILABLE_TIMEOUT_MILLISECONDS = 10 * 60 * 1000; // 10 minutes.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache used to verify if an alternative thumbnails exists for a given video id.
|
* Cache used to verify if an alternative thumbnails exists for a given video ID.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
private static final Map<String, VerifiedQualities> altVideoIdLookup =
|
private static final Map<String, VerifiedQualities> altVideoIdLookup =
|
||||||
@@ -546,10 +546,10 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static boolean verifyAltThumbnailExist(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
static boolean verifyAltThumbnailExist(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
||||||
@NonNull String imageUrl) {
|
@NonNull String imageURL) {
|
||||||
VerifiedQualities verified = getVerifiedQualities(videoId, Settings.ALT_THUMBNAIL_STILLS_FAST.get());
|
VerifiedQualities verified = getVerifiedQualities(videoId, Settings.ALT_THUMBNAIL_STILLS_FAST.get());
|
||||||
if (verified == null) return true; // Fast alt thumbnails is enabled.
|
if (verified == null) return true; // Fast alt thumbnails is enabled.
|
||||||
return verified.verifyYouTubeThumbnailExists(videoId, quality, imageUrl);
|
return verified.verifyYouTubeThumbnailExists(videoId, quality, imageURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setAltThumbnailDoesNotExist(@NonNull String videoId, @NonNull ThumbnailQuality quality) {
|
static void setAltThumbnailDoesNotExist(@NonNull String videoId, @NonNull ThumbnailQuality quality) {
|
||||||
@@ -590,10 +590,10 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify if a video alt thumbnail exists. Does so by making a minimal HEAD http request.
|
* Verify if a video alt thumbnail exists. Does so by making a minimal HEAD HTTP request.
|
||||||
*/
|
*/
|
||||||
synchronized boolean verifyYouTubeThumbnailExists(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
synchronized boolean verifyYouTubeThumbnailExists(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
||||||
@NonNull String imageUrl) {
|
@NonNull String imageURL) {
|
||||||
if (highestQualityVerified != null && highestQualityVerified.ordinal() >= quality.ordinal()) {
|
if (highestQualityVerified != null && highestQualityVerified.ordinal() >= quality.ordinal()) {
|
||||||
return true; // Previously verified as existing.
|
return true; // Previously verified as existing.
|
||||||
}
|
}
|
||||||
@@ -609,7 +609,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fastQuality) {
|
if (fastQuality) {
|
||||||
return true; // Unknown if it exists or not. Use the URL anyways and update afterwards if loading fails.
|
return true; // Unknown if it exists or not. Use the URL anyway and update afterward if loading fails.
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean imageFileFound;
|
boolean imageFileFound;
|
||||||
@@ -619,7 +619,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
imageFileFound = Utils.submitOnBackgroundThread(() -> {
|
imageFileFound = Utils.submitOnBackgroundThread(() -> {
|
||||||
final int connectionTimeoutMillis = 10000; // 10 seconds.
|
final int connectionTimeoutMillis = 10000; // 10 seconds.
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(imageURL).openConnection();
|
||||||
connection.setConnectTimeout(connectionTimeoutMillis);
|
connection.setConnectTimeout(connectionTimeoutMillis);
|
||||||
connection.setReadTimeout(connectionTimeoutMillis);
|
connection.setReadTimeout(connectionTimeoutMillis);
|
||||||
connection.setRequestMethod("HEAD");
|
connection.setRequestMethod("HEAD");
|
||||||
@@ -632,13 +632,13 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
return (contentType != null && contentType.startsWith("image"));
|
return (contentType != null && contentType.startsWith("image"));
|
||||||
}
|
}
|
||||||
if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) {
|
if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
Logger.printDebug(() -> "Unexpected response code: " + responseCode + " for url: " + imageUrl);
|
Logger.printDebug(() -> "Unexpected response code: " + responseCode + " for URL: " + imageURL);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}).get();
|
}).get();
|
||||||
Logger.printDebug(() -> "Verification took: " + (System.currentTimeMillis() - start) + "ms for image: " + imageUrl);
|
Logger.printDebug(() -> "Verification took: " + (System.currentTimeMillis() - start) + "ms for image: " + imageURL);
|
||||||
} catch (ExecutionException | InterruptedException ex) {
|
} catch (ExecutionException | InterruptedException ex) {
|
||||||
Logger.printInfo(() -> "Could not verify alt url: " + imageUrl, ex);
|
Logger.printInfo(() -> "Could not verify alt URL: " + imageURL, ex);
|
||||||
imageFileFound = false;
|
imageFileFound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,11 +650,11 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
/**
|
/**
|
||||||
* YouTube video thumbnail url, decoded into it's relevant parts.
|
* YouTube video thumbnail url, decoded into it's relevant parts.
|
||||||
*/
|
*/
|
||||||
private static class DecodedThumbnailUrl {
|
private static class DecodedThumbnailURL {
|
||||||
private static final String YOUTUBE_THUMBNAIL_DOMAIN = "https://i.ytimg.com/";
|
private static final String YOUTUBE_THUMBNAIL_DOMAIN = "https://i.ytimg.com/";
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static DecodedThumbnailUrl decodeImageUrl(String url) {
|
static DecodedThumbnailURL decodeImageURL(String url) {
|
||||||
final int urlPathStartIndex = url.indexOf('/', "https://".length()) + 1;
|
final int urlPathStartIndex = url.indexOf('/', "https://".length()) + 1;
|
||||||
if (urlPathStartIndex <= 0) return null;
|
if (urlPathStartIndex <= 0) return null;
|
||||||
|
|
||||||
@@ -674,14 +674,14 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex);
|
int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex);
|
||||||
if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length();
|
if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length();
|
||||||
|
|
||||||
return new DecodedThumbnailUrl(url, urlPathStartIndex, urlPathEndIndex, videoIdStartIndex, videoIdEndIndex,
|
return new DecodedThumbnailURL(url, urlPathStartIndex, urlPathEndIndex, videoIdStartIndex, videoIdEndIndex,
|
||||||
imageSizeStartIndex, imageSizeEndIndex, imageExtensionEndIndex);
|
imageSizeStartIndex, imageSizeEndIndex, imageExtensionEndIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String originalFullUrl;
|
final String originalFullURL;
|
||||||
/** Full usable url, but stripped of any tracking information. */
|
/** Full usable url, but stripped of any tracking information. */
|
||||||
final String sanitizedUrl;
|
final String sanitizedURL;
|
||||||
/** Url path, such as 'vi' or 'vi_webp' */
|
/** URL path, such as 'vi' or 'vi_webp' */
|
||||||
final String urlPath;
|
final String urlPath;
|
||||||
final String videoId;
|
final String videoId;
|
||||||
/** Quality, such as hq720 or sddefault. */
|
/** Quality, such as hq720 or sddefault. */
|
||||||
@@ -691,25 +691,25 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
/** User view tracking parameters, only present on some images. */
|
/** User view tracking parameters, only present on some images. */
|
||||||
final String viewTrackingParameters;
|
final String viewTrackingParameters;
|
||||||
|
|
||||||
DecodedThumbnailUrl(String fullUrl, int urlPathStartIndex, int urlPathEndIndex, int videoIdStartIndex, int videoIdEndIndex,
|
DecodedThumbnailURL(String fullURL, int urlPathStartIndex, int urlPathEndIndex, int videoIdStartIndex, int videoIdEndIndex,
|
||||||
int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) {
|
int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) {
|
||||||
originalFullUrl = fullUrl;
|
originalFullURL = fullURL;
|
||||||
sanitizedUrl = fullUrl.substring(0, imageExtensionEndIndex);
|
sanitizedURL = fullURL.substring(0, imageExtensionEndIndex);
|
||||||
urlPath = fullUrl.substring(urlPathStartIndex, urlPathEndIndex);
|
urlPath = fullURL.substring(urlPathStartIndex, urlPathEndIndex);
|
||||||
videoId = fullUrl.substring(videoIdStartIndex, videoIdEndIndex);
|
videoId = fullURL.substring(videoIdStartIndex, videoIdEndIndex);
|
||||||
imageQuality = fullUrl.substring(imageSizeStartIndex, imageSizeEndIndex);
|
imageQuality = fullURL.substring(imageSizeStartIndex, imageSizeEndIndex);
|
||||||
imageExtension = fullUrl.substring(imageSizeEndIndex + 1, imageExtensionEndIndex);
|
imageExtension = fullURL.substring(imageSizeEndIndex + 1, imageExtensionEndIndex);
|
||||||
viewTrackingParameters = (imageExtensionEndIndex == fullUrl.length())
|
viewTrackingParameters = (imageExtensionEndIndex == fullURL.length())
|
||||||
? "" : fullUrl.substring(imageExtensionEndIndex);
|
? "" : fullURL.substring(imageExtensionEndIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
String createStillsUrl(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) {
|
String createStillsURL(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) {
|
||||||
// Images could be upgraded to webp if they are not already, but this fails quite often,
|
// Images could be upgraded to webp if they are not already, but this fails quite often,
|
||||||
// especially for new videos uploaded in the last hour.
|
// especially for new videos uploaded in the last hour.
|
||||||
// And even if alt webp images do exist, sometimes they can load much slower than the original jpg alt images.
|
// And even if alt webp images do exist, sometimes they can load much slower than the original jpg alt images.
|
||||||
// (as much as 4x slower network response has been observed, despite the alt webp image being a smaller file).
|
// (as much as 4x slower network response has been observed, despite the alt webp image being a smaller file).
|
||||||
StringBuilder builder = new StringBuilder(originalFullUrl.length() + 2);
|
StringBuilder builder = new StringBuilder(originalFullURL.length() + 2);
|
||||||
// Many different "i.ytimage.com" domains exist such as "i9.ytimg.com",
|
// Many different "i.ytimage.com" domains exist such as "i9.ytimg.com",
|
||||||
// but still captures are frequently not available on the other domains (especially newly uploaded videos).
|
// but still captures are frequently not available on the other domains (especially newly uploaded videos).
|
||||||
// So always use the primary domain for a higher success rate.
|
// So always use the primary domain for a higher success rate.
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ public class BackgroundPlaybackPatch {
|
|||||||
|
|
||||||
// Steps to verify most edge cases (with Shorts background playback set to off):
|
// Steps to verify most edge cases (with Shorts background playback set to off):
|
||||||
// 1. Open a regular video
|
// 1. Open a regular video
|
||||||
// 2. Minimize app (PIP should appear)
|
// 2. Minimize app (PiP should appear)
|
||||||
// 3. Reopen app
|
// 3. Reopen app
|
||||||
// 4. Open a Short (without closing the regular video)
|
// 4. Open a Short (without closing the regular video)
|
||||||
// (try opening both Shorts in the video player suggestions AND Shorts from the home feed)
|
// (try opening both Shorts in the video player suggestions AND Shorts from the home feed)
|
||||||
@@ -23,7 +23,7 @@ public class BackgroundPlaybackPatch {
|
|||||||
// 6. Reopen app
|
// 6. Reopen app
|
||||||
// 7. Close the Short
|
// 7. Close the Short
|
||||||
// 8. Resume playing the regular video
|
// 8. Resume playing the regular video
|
||||||
// 9. Minimize the app (PIP should appear)
|
// 9. Minimize the app (PiP should appear)
|
||||||
if (ShortsPlayerState.isOpen()) {
|
if (ShortsPlayerState.isOpen()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -15,24 +15,24 @@ public final class BypassImageRegionRestrictionsPatch {
|
|||||||
private static final String REPLACEMENT_IMAGE_DOMAIN = "https://yt4.ggpht.com";
|
private static final String REPLACEMENT_IMAGE_DOMAIN = "https://yt4.ggpht.com";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube static images domain. Includes user and channel avatar images and community post images.
|
* YouTube static images' domain. Includes user and channel avatar images and community post images.
|
||||||
*/
|
*/
|
||||||
private static final Pattern YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
private static final Pattern YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
||||||
= Pattern.compile("^https://(yt3|lh[3-6]|play-lh)\\.(ggpht|googleusercontent)\\.com");
|
= Pattern.compile("^https://(yt3|lh[3-6]|play-lh)\\.(ggpht|googleusercontent)\\.com");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread and by multiple threads at the same time.
|
* Injection point. Called off the main thread and by multiple threads at the same time.
|
||||||
*
|
*
|
||||||
* @param originalUrl Image url for all image urls loaded.
|
* @param originalURL Image URL for all image URLs loaded.
|
||||||
*/
|
*/
|
||||||
public static String overrideImageURL(String originalUrl) {
|
public static String overrideImageURL(String originalURL) {
|
||||||
try {
|
try {
|
||||||
if (BYPASS_IMAGE_REGION_RESTRICTIONS_ENABLED) {
|
if (BYPASS_IMAGE_REGION_RESTRICTIONS_ENABLED) {
|
||||||
String replacement = YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
String replacement = YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
||||||
.matcher(originalUrl).replaceFirst(REPLACEMENT_IMAGE_DOMAIN);
|
.matcher(originalURL).replaceFirst(REPLACEMENT_IMAGE_DOMAIN);
|
||||||
|
|
||||||
if (Settings.DEBUG.get() && !replacement.equals(originalUrl)) {
|
if (Settings.DEBUG.get() && !replacement.equals(originalURL)) {
|
||||||
Logger.printDebug(() -> "Replaced: '" + originalUrl + "' with: '" + replacement + "'");
|
Logger.printDebug(() -> "Replaced: '" + originalURL + "' with: '" + replacement + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return replacement;
|
return replacement;
|
||||||
@@ -41,6 +41,6 @@ public final class BypassImageRegionRestrictionsPatch {
|
|||||||
Logger.printException(() -> "overrideImageURL failure", ex);
|
Logger.printException(() -> "overrideImageURL failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalUrl;
|
return originalURL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ public class ChangeFormFactorPatch {
|
|||||||
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||||
// On first startup of the app the navigation buttons are fetched and updated.
|
// On first startup of the app the navigation buttons are fetched and updated.
|
||||||
// If the user immediately opens the 'You' or opens a video, then the call to
|
// If the user immediately opens the 'You' or opens a video, then the call to
|
||||||
// update the navigtation buttons will use the non automotive form factor
|
// update the navigation buttons will use the non-automotive form factor
|
||||||
// and the explore tab is missing.
|
// and the explore tab is missing.
|
||||||
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
||||||
// For now, always hide the explore tab.
|
// For now, always hide the explore tab.
|
||||||
|
|||||||
+3
-3
@@ -24,7 +24,7 @@ public final class ChangeStartPagePatch {
|
|||||||
DEFAULT("", null),
|
DEFAULT("", null),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse id.
|
* BrowseId.
|
||||||
*/
|
*/
|
||||||
ALL_SUBSCRIPTIONS("FEchannels", TRUE),
|
ALL_SUBSCRIPTIONS("FEchannels", TRUE),
|
||||||
BROWSE("FEguide_builder", TRUE),
|
BROWSE("FEguide_builder", TRUE),
|
||||||
@@ -39,7 +39,7 @@ public final class ChangeStartPagePatch {
|
|||||||
YOUR_CLIPS("FEclips", TRUE),
|
YOUR_CLIPS("FEclips", TRUE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel id, this can be used as a browseId.
|
* Channel ID, this can be used as a browseId.
|
||||||
*/
|
*/
|
||||||
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
|
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
|
||||||
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
|
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
|
||||||
@@ -52,7 +52,7 @@ public final class ChangeStartPagePatch {
|
|||||||
VIRTUAL_REALITY("UCzuqhhs6NWbgTzMuM09WKDQ", TRUE),
|
VIRTUAL_REALITY("UCzuqhhs6NWbgTzMuM09WKDQ", TRUE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playlist id, this can be used as a browseId.
|
* Playlist ID, this can be used as a browseId.
|
||||||
*/
|
*/
|
||||||
LIKED_VIDEO("VLLL", TRUE),
|
LIKED_VIDEO("VLLL", TRUE),
|
||||||
WATCH_LATER("VLWL", TRUE),
|
WATCH_LATER("VLWL", TRUE),
|
||||||
|
|||||||
+4
-4
@@ -7,9 +7,9 @@ import android.os.Build;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
public class CopyVideoUrlPatch {
|
public class CopyVideoURLPatch {
|
||||||
|
|
||||||
public static void copyUrl(boolean withTimestamp) {
|
public static void copyURL(boolean withTimestamp) {
|
||||||
try {
|
try {
|
||||||
StringBuilder builder = new StringBuilder("https://youtu.be/");
|
StringBuilder builder = new StringBuilder("https://youtu.be/");
|
||||||
builder.append(VideoInformation.getVideoId());
|
builder.append(VideoInformation.getVideoId());
|
||||||
@@ -31,7 +31,7 @@ public class CopyVideoUrlPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Utils.setClipboard(builder.toString());
|
Utils.setClipboard(builder.toString());
|
||||||
// Do not show a toast if using Android 13+ as it shows it's own toast.
|
// Do not show a toast if using Android 13+ as it shows its own toast.
|
||||||
// But if the user copied with a timestamp then show a toast.
|
// But if the user copied with a timestamp then show a toast.
|
||||||
// Unfortunately this will show 2 toasts on Android 13+, but no way around this.
|
// Unfortunately this will show 2 toasts on Android 13+, but no way around this.
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || (withTimestamp && currentVideoTimeInSeconds > 0)) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || (withTimestamp && currentVideoTimeInSeconds > 0)) {
|
||||||
@@ -40,7 +40,7 @@ public class CopyVideoUrlPatch {
|
|||||||
: str("revanced_share_copy_url_success"));
|
: str("revanced_share_copy_url_success"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.printException(() -> "Failed to generate video url", e);
|
Logger.printException(() -> "Failed to generate video URL", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+39
-2
@@ -1,5 +1,8 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -12,6 +15,13 @@ public class DisableHapticFeedbackPatch {
|
|||||||
return Settings.DISABLE_HAPTIC_FEEDBACK_CHAPTERS.get();
|
return Settings.DISABLE_HAPTIC_FEEDBACK_CHAPTERS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean disablePreciseSeekingVibrate() {
|
||||||
|
return Settings.DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -22,8 +32,10 @@ public class DisableHapticFeedbackPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean disablePreciseSeekingVibrate() {
|
public static Object disableTapAndHoldVibrate(Object vibrator) {
|
||||||
return Settings.DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING.get();
|
return Settings.DISABLE_HAPTIC_FEEDBACK_TAP_AND_HOLD.get()
|
||||||
|
? null
|
||||||
|
: vibrator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,4 +44,29 @@ public class DisableHapticFeedbackPatch {
|
|||||||
public static boolean disableZoomVibrate() {
|
public static boolean disableZoomVibrate() {
|
||||||
return Settings.DISABLE_HAPTIC_FEEDBACK_ZOOM.get();
|
return Settings.DISABLE_HAPTIC_FEEDBACK_ZOOM.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void vibrate(Vibrator vibrator, VibrationEffect vibrationEffect) {
|
||||||
|
if (disableVibrate()) return;
|
||||||
|
vibrator.vibrate(vibrationEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static void vibrate(Vibrator vibrator, long milliseconds) {
|
||||||
|
if (disableVibrate()) return;
|
||||||
|
vibrator.vibrate(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean disableVibrate() {
|
||||||
|
return Settings.DISABLE_HAPTIC_FEEDBACK_CHAPTERS.get()
|
||||||
|
&& Settings.DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING.get()
|
||||||
|
&& Settings.DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO.get()
|
||||||
|
&& Settings.DISABLE_HAPTIC_FEEDBACK_TAP_AND_HOLD.get()
|
||||||
|
&& Settings.DISABLE_HAPTIC_FEEDBACK_ZOOM.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -4,8 +4,10 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class DisablePlayerPopupPanelsPatch {
|
public class DisablePlayerPopupPanelsPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.playerpopuppanels.patch.PlayerPopupPanelsPatch
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean disablePlayerPopupPanels() {
|
public static boolean disablePlayerPopupPanels() {
|
||||||
return Settings.PLAYER_POPUP_PANELS.get();
|
return Settings.DISABLE_PLAYER_POPUP_PANELS.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -3,12 +3,12 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class DisableSignInToTvPopupPatch {
|
public class DisableSignInToTVPopupPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean disableSignInToTvPopup() {
|
public static boolean disableSignInToTvPopup() {
|
||||||
return Settings.DISABLE_SIGNIN_TO_TV_POPUP.get();
|
return Settings.DISABLE_SIGN_IN_TO_TV_POPUP.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -28,7 +28,7 @@ public final class DownloadsPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
* <p>
|
||||||
* Called from the in app download hook,
|
* Called from the in-app download hook,
|
||||||
* for both the player action button (below the video)
|
* for both the player action button (below the video)
|
||||||
* and the 'Download video' flyout option for feed videos.
|
* and the 'Download video' flyout option for feed videos.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -41,7 +41,7 @@ public final class DownloadsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If possible, use the main activity as the context.
|
// If possible, use the main activity as the context.
|
||||||
// Otherwise fall back on using the application context.
|
// Otherwise, fall back on using the application context.
|
||||||
Context context = activityRef.get();
|
Context context = activityRef.get();
|
||||||
boolean isActivityContext = true;
|
boolean isActivityContext = true;
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
|
|||||||
+5
-3
@@ -20,8 +20,10 @@ public class ExitFullscreenPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void endOfVideoReached() {
|
public static void endOfVideoReached(Enum<?> status) {
|
||||||
try {
|
try {
|
||||||
|
if (status == null || !"ENDED".equals(status.name())) return;
|
||||||
|
|
||||||
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
|
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
|
||||||
if (mode == FullscreenMode.DISABLED) {
|
if (mode == FullscreenMode.DISABLED) {
|
||||||
return;
|
return;
|
||||||
@@ -43,7 +45,7 @@ public class ExitFullscreenPatch {
|
|||||||
// set because the overlay controls are not attached.
|
// set because the overlay controls are not attached.
|
||||||
// To fix this, push the perform click to the back fo the main thread,
|
// To fix this, push the perform click to the back fo the main thread,
|
||||||
// and by then the overlay controls will be visible since the video is now finished.
|
// and by then the overlay controls will be visible since the video is now finished.
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
ImageView button = PlayerControlsPatch.fullscreenButtonRef.get();
|
ImageView button = PlayerControlsPatch.fullscreenButtonRef.get();
|
||||||
if (button == null) {
|
if (button == null) {
|
||||||
Logger.printDebug(() -> "Fullscreen button is null, cannot click");
|
Logger.printDebug(() -> "Fullscreen button is null, cannot click");
|
||||||
@@ -54,7 +56,7 @@ public class ExitFullscreenPatch {
|
|||||||
button.performClick();
|
button.performClick();
|
||||||
button.setSoundEffectsEnabled(soundEffectsEnabled);
|
button.setSoundEffectsEnabled(soundEffectsEnabled);
|
||||||
}
|
}
|
||||||
});
|
}, 10);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "endOfVideoReached failure", ex);
|
Logger.printException(() -> "endOfVideoReached failure", ex);
|
||||||
|
|||||||
+3
-3
@@ -3,11 +3,11 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class HideGetPremiumPatch {
|
public final class HideAutoplayPreviewPatch {
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean hideGetPremiumView() {
|
public static boolean hideAutoplayPreview() {
|
||||||
return Settings.HIDE_GET_PREMIUM.get();
|
return Settings.HIDE_AUTOPLAY_PREVIEW.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -12,13 +12,13 @@ public class HideEndScreenCardsPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void hideEndScreenCardView(View view) {
|
public static void hideEndScreenCardView(View view) {
|
||||||
Utils.hideViewUnderCondition(Settings.HIDE_ENDSCREEN_CARDS, view);
|
Utils.hideViewUnderCondition(Settings.HIDE_END_SCREEN_CARDS, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean hideEndScreenCards() {
|
public static boolean hideEndScreenCards() {
|
||||||
return Settings.HIDE_ENDSCREEN_CARDS.get();
|
return Settings.HIDE_END_SCREEN_CARDS.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+52
-2
@@ -19,7 +19,7 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean hideAutoPlayButton() {
|
public static boolean hideAutoplayButton() {
|
||||||
return HIDE_AUTOPLAY_BUTTON_ENABLED;
|
return HIDE_AUTOPLAY_BUTTON_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +46,41 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
imageView.setVisibility(Settings.HIDE_CAPTIONS_BUTTON.get() ? ImageView.GONE : ImageView.VISIBLE);
|
imageView.setVisibility(Settings.HIDE_CAPTIONS_BUTTON.get() ? ImageView.GONE : ImageView.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideCollapseButton(ImageView imageView) {
|
||||||
|
if (!Settings.HIDE_COLLAPSE_BUTTON.get()) return;
|
||||||
|
|
||||||
|
// Make the collapse button invisible
|
||||||
|
imageView.setImageResource(android.R.color.transparent);
|
||||||
|
imageView.setImageAlpha(0);
|
||||||
|
imageView.setEnabled(false);
|
||||||
|
|
||||||
|
// Adjust layout params if RelativeLayout
|
||||||
|
var layoutParams = imageView.getLayoutParams();
|
||||||
|
if (layoutParams instanceof android.widget.RelativeLayout.LayoutParams) {
|
||||||
|
android.widget.RelativeLayout.LayoutParams lp = new android.widget.RelativeLayout.LayoutParams(0, 0);
|
||||||
|
imageView.setLayoutParams(lp);
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Unknown collapse button layout params: " + layoutParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setTitleAnchorStartMargin(View titleAnchorView) {
|
||||||
|
if (!Settings.HIDE_COLLAPSE_BUTTON.get()) return;
|
||||||
|
|
||||||
|
var layoutParams = titleAnchorView.getLayoutParams();
|
||||||
|
if (layoutParams instanceof android.widget.RelativeLayout.LayoutParams relativeParams) {
|
||||||
|
relativeParams.setMarginStart(0);
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Unknown title anchor layout params: " + layoutParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final boolean HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS_ENABLED
|
private static final boolean HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS_ENABLED
|
||||||
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
|
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
|
||||||
|
|
||||||
@@ -64,13 +99,28 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must use a deferred call to main thread to hide the button.
|
// Must use a deferred call to main thread to hide the button.
|
||||||
// Otherwise the layout crashes if set to hidden now.
|
// Otherwise, the layout crashes if set to hidden now.
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
hideView(parentView, PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID);
|
hideView(parentView, PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID);
|
||||||
hideView(parentView, PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID);
|
hideView(parentView, PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static ImageView hideFullscreenButton(ImageView imageView) {
|
||||||
|
if (!Settings.HIDE_FULLSCREEN_BUTTON.get()) {
|
||||||
|
return imageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageView != null) {
|
||||||
|
imageView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+7
-2
@@ -7,7 +7,12 @@ public class LoopVideoPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
public static boolean shouldLoopVideo() {
|
public static boolean shouldLoopVideo(Enum<?> status) {
|
||||||
return Settings.LOOP_VIDEO.get();
|
boolean shouldLoop = status != null && "ENDED".equals(status.name())
|
||||||
|
&& Settings.LOOP_VIDEO.get();
|
||||||
|
|
||||||
|
// Instead of calling a method to loop the video, just seek to 00:00.
|
||||||
|
if (shouldLoop) VideoInformation.seekTo(0);
|
||||||
|
return shouldLoop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-6
@@ -13,6 +13,7 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
import app.revanced.extension.shared.ResourceType;
|
||||||
@@ -29,7 +30,6 @@ public final class MiniplayerPatch {
|
|||||||
public enum MiniplayerType {
|
public enum MiniplayerType {
|
||||||
/**
|
/**
|
||||||
* Disabled. When swiped down the miniplayer is immediately closed.
|
* Disabled. When swiped down the miniplayer is immediately closed.
|
||||||
* Only available with 19.43+
|
|
||||||
*/
|
*/
|
||||||
DISABLED(false, null),
|
DISABLED(false, null),
|
||||||
/** Unmodified type, and same as un-patched. */
|
/** Unmodified type, and same as un-patched. */
|
||||||
@@ -89,9 +89,9 @@ public final class MiniplayerPatch {
|
|||||||
final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding.
|
final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding.
|
||||||
// Round down to the nearest 5 pixels, to keep any error toasts easier to read.
|
// Round down to the nearest 5 pixels, to keep any error toasts easier to read.
|
||||||
final int estimatedWidthDipMax = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5);
|
final int estimatedWidthDipMax = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5);
|
||||||
// On some ultra low end devices the pixel width and density are the same number,
|
// On some ultra-low-end devices the pixel width and density are the same number,
|
||||||
// which causes the estimate to always give a value of 1.
|
// which causes the estimate to always give a value of 1.
|
||||||
// Fix this by using a fixed size of double the min width.
|
// Fix this by using a fixed size twice the minimum width.
|
||||||
final int WIDTH_DIP_MAX = estimatedWidthDipMax <= WIDTH_DIP_MIN
|
final int WIDTH_DIP_MAX = estimatedWidthDipMax <= WIDTH_DIP_MIN
|
||||||
? 2 * WIDTH_DIP_MIN
|
? 2 * WIDTH_DIP_MIN
|
||||||
: estimatedWidthDipMax;
|
: estimatedWidthDipMax;
|
||||||
@@ -140,8 +140,8 @@ public final class MiniplayerPatch {
|
|||||||
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3 || CURRENT_TYPE == MODERN_4)
|
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3 || CURRENT_TYPE == MODERN_4)
|
||||||
&& Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
&& Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
||||||
|
|
||||||
// 19.25 is last version that has forward/back buttons for phones,
|
// 19.25 is last version that uses forward/back buttons for phones,
|
||||||
// but buttons still show for tablets/foldable devices and they don't work well so always hide.
|
// but buttons still show for tablets/foldable devices, and they don't work well so always hide.
|
||||||
private static final boolean HIDE_REWIND_FORWARD_ENABLED = CURRENT_TYPE == MODERN_1
|
private static final boolean HIDE_REWIND_FORWARD_ENABLED = CURRENT_TYPE == MODERN_1
|
||||||
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
|
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
|
||||||
|
|
||||||
@@ -281,6 +281,12 @@ public final class MiniplayerPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getModernMiniplayerOverrideType(int original) {
|
public static int getModernMiniplayerOverrideType(int original) {
|
||||||
|
if (CURRENT_TYPE == MINIMAL) {
|
||||||
|
// In newer app targets the minimal player can show the wrong icon if modern 4 is allowed.
|
||||||
|
// Forcing to modern 1 seems to work.
|
||||||
|
return Objects.requireNonNull(MODERN_1.modernPlayerType);
|
||||||
|
}
|
||||||
|
|
||||||
Integer modernValue = CURRENT_TYPE.modernPlayerType;
|
Integer modernValue = CURRENT_TYPE.modernPlayerType;
|
||||||
return modernValue == null
|
return modernValue == null
|
||||||
? original
|
? original
|
||||||
@@ -385,7 +391,7 @@ public final class MiniplayerPatch {
|
|||||||
public static boolean allowBoldIcons(boolean original) {
|
public static boolean allowBoldIcons(boolean original) {
|
||||||
if (CURRENT_TYPE == MINIMAL) {
|
if (CURRENT_TYPE == MINIMAL) {
|
||||||
// Minimal player does not have the correct pause/play icon (it's too large).
|
// Minimal player does not have the correct pause/play icon (it's too large).
|
||||||
// Use the non bold icons instead.
|
// Use the non-bold icons instead.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
||||||
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class NavigationBarPatch {
|
||||||
|
|
||||||
|
private static final Map<NavigationButton, Boolean> shouldHideMap = new EnumMap<>(NavigationButton.class) {
|
||||||
|
{
|
||||||
|
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
||||||
|
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
||||||
|
put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NOTIFICATIONS_BUTTON.get());
|
||||||
|
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
||||||
|
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||||
|
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||||
|
|
||||||
|
private static final boolean DISABLE_TRANSLUCENT_STATUS_BAR
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
|
||||||
|
|
||||||
|
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
|
||||||
|
|
||||||
|
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
|
||||||
|
|
||||||
|
private static final boolean NARROW_NAVIGATION_BUTTONS
|
||||||
|
= Settings.NARROW_NAVIGATION_BUTTONS.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String switchCreateWithNotificationButton(String osName) {
|
||||||
|
return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||||
|
? "Android Automotive"
|
||||||
|
: osName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||||
|
if (Boolean.TRUE.equals(shouldHideMap.get(button))) {
|
||||||
|
tabView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideNavigationButtonLabels(TextView navigationLabelsView) {
|
||||||
|
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useAnimatedNavigationButtons(boolean original) {
|
||||||
|
return Settings.NAVIGATION_BAR_ANIMATIONS.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean enableNarrowNavigationButton(boolean original) {
|
||||||
|
return NARROW_NAVIGATION_BUTTONS || original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useTranslucentNavigationStatusBar(boolean original) {
|
||||||
|
// Must check Android version, as forcing this on Android 11 or lower causes app hang and crash.
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DISABLE_TRANSLUCENT_STATUS_BAR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useTranslucentNavigationButtons(boolean original) {
|
||||||
|
// Feature requires Android 13+
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.isDarkModeEnabled()
|
||||||
|
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||||
|
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
|
||||||
|
public static void hideCreateButton(String enumString, View view) {
|
||||||
|
if (!Settings.HIDE_TOOLBAR_CREATE_BUTTON.get())
|
||||||
|
return;
|
||||||
|
|
||||||
|
hideViewUnderCondition(isCreateButton(enumString), view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideNotificationButton(String enumString, View view) {
|
||||||
|
if (!Settings.HIDE_TOOLBAR_NOTIFICATION_BUTTON.get())
|
||||||
|
return;
|
||||||
|
|
||||||
|
hideViewUnderCondition(isNotificationButton(enumString), view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideSearchButton(String enumString, View view) {
|
||||||
|
if (!Settings.HIDE_TOOLBAR_SEARCH_BUTTON.get())
|
||||||
|
return;
|
||||||
|
|
||||||
|
hideViewUnderCondition(isSearchButton(enumString), view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideSearchButton(MenuItem menuItem, int original) {
|
||||||
|
menuItem.setShowAsAction(
|
||||||
|
Settings.HIDE_TOOLBAR_SEARCH_BUTTON.get()
|
||||||
|
? MenuItem.SHOW_AS_ACTION_NEVER
|
||||||
|
: original
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCreateButton(String enumString) {
|
||||||
|
return "CREATION_ENTRY".equals(enumString) // Create button for Phone layout.
|
||||||
|
|| "FAB_CAMERA".equals(enumString); // Create button for Tablet layout.
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNotificationButton(String enumString) {
|
||||||
|
return "TAB_ACTIVITY".equals(enumString) // Notification button.
|
||||||
|
|| "TAB_ACTIVITY_CAIRO".equals(enumString); // Notification button (New layout).
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSearchButton(String enumString) {
|
||||||
|
return "SEARCH".equals(enumString) // Search button.
|
||||||
|
|| "SEARCH_CAIRO".equals(enumString) // Search button (New layout).
|
||||||
|
|| "SEARCH_BOLD".equals(enumString); // Search button (Shorts).
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wide searchbar
|
||||||
|
private static final Boolean WIDE_SEARCHBAR_ENABLED = Settings.WIDE_SEARCHBAR.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean enableWideSearchbar(boolean original) {
|
||||||
|
return WIDE_SEARCHBAR_ENABLED || original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setActionBar(View view) {
|
||||||
|
try {
|
||||||
|
if (!WIDE_SEARCHBAR_ENABLED) return;
|
||||||
|
|
||||||
|
View searchBarView = Utils.getChildViewByResourceName(view, "search_bar");
|
||||||
|
|
||||||
|
final int paddingLeft = searchBarView.getPaddingLeft();
|
||||||
|
final int paddingRight = searchBarView.getPaddingRight();
|
||||||
|
final int paddingTop = searchBarView.getPaddingTop();
|
||||||
|
final int paddingBottom = searchBarView.getPaddingBottom();
|
||||||
|
final int paddingStart = Dim.dp8;
|
||||||
|
|
||||||
|
if (Utils.isRightToLeftLocale()) {
|
||||||
|
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
|
||||||
|
} else {
|
||||||
|
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "setActionBar failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-108
@@ -1,108 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
|
||||||
|
|
||||||
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
|
||||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class NavigationButtonsPatch {
|
|
||||||
|
|
||||||
private static final Map<NavigationButton, Boolean> shouldHideMap = new EnumMap<>(NavigationButton.class) {
|
|
||||||
{
|
|
||||||
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
|
||||||
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
|
||||||
put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NOTIFICATIONS_BUTTON.get());
|
|
||||||
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
|
||||||
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
|
||||||
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
|
||||||
|
|
||||||
private static final boolean DISABLE_TRANSLUCENT_STATUS_BAR
|
|
||||||
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
|
|
||||||
|
|
||||||
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
|
|
||||||
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
|
|
||||||
|
|
||||||
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
|
||||||
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean switchCreateWithNotificationButton() {
|
|
||||||
return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
|
||||||
if (Boolean.TRUE.equals(shouldHideMap.get(button))) {
|
|
||||||
tabView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void hideNavigationButtonLabels(TextView navigationLabelsView) {
|
|
||||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean useAnimatedNavigationButtons(boolean original) {
|
|
||||||
return Settings.NAVIGATION_BAR_ANIMATIONS.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean useTranslucentNavigationStatusBar(boolean original) {
|
|
||||||
// Must check Android version, as forcing this on Android 11 or lower causes app hang and crash.
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DISABLE_TRANSLUCENT_STATUS_BAR) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean useTranslucentNavigationButtons(boolean original) {
|
|
||||||
// Feature requires Android 13+
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utils.isDarkModeEnabled()
|
|
||||||
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
|
||||||
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-4
@@ -31,6 +31,13 @@ public class OpenShortsInRegularPlayerPatch {
|
|||||||
mainActivityRef = new WeakReference<>(activity);
|
mainActivityRef = new WeakReference<>(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean overrideBackPressToExit() {
|
||||||
|
return overrideBackPressToExit(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -46,7 +53,7 @@ public class OpenShortsInRegularPlayerPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean openShort(String videoID) {
|
public static boolean openShort(String videoId) {
|
||||||
try {
|
try {
|
||||||
ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get();
|
ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get();
|
||||||
if (type == ShortsPlayerType.SHORTS_PLAYER) {
|
if (type == ShortsPlayerType.SHORTS_PLAYER) {
|
||||||
@@ -54,7 +61,7 @@ public class OpenShortsInRegularPlayerPatch {
|
|||||||
return false; // Default unpatched behavior.
|
return false; // Default unpatched behavior.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoID.isEmpty()) {
|
if (videoId.isEmpty()) {
|
||||||
// Shorts was opened using launcher app shortcut.
|
// Shorts was opened using launcher app shortcut.
|
||||||
//
|
//
|
||||||
// This check will not detect if the Shorts app shortcut is used
|
// This check will not detect if the Shorts app shortcut is used
|
||||||
@@ -84,12 +91,12 @@ public class OpenShortsInRegularPlayerPatch {
|
|||||||
// Can use the application context and add intent flags of
|
// Can use the application context and add intent flags of
|
||||||
// FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP
|
// FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP
|
||||||
// But the activity context seems to fix random app crashes
|
// But the activity context seems to fix random app crashes
|
||||||
// if Shorts urls are opened outside the app.
|
// if Shorts URLs are opened outside the app.
|
||||||
var context = mainActivityRef.get();
|
var context = mainActivityRef.get();
|
||||||
|
|
||||||
Intent videoPlayerIntent = new Intent(
|
Intent videoPlayerIntent = new Intent(
|
||||||
Intent.ACTION_VIEW,
|
Intent.ACTION_VIEW,
|
||||||
Uri.parse("https://youtube.com/watch?v=" + videoID)
|
Uri.parse("https://youtube.com/watch?v=" + videoId)
|
||||||
);
|
);
|
||||||
videoPlayerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
videoPlayerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
videoPlayerIntent.setPackage(context.getPackageName());
|
videoPlayerIntent.setPackage(context.getPackageName());
|
||||||
|
|||||||
+83
-10
@@ -1,6 +1,12 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
@@ -8,22 +14,89 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class RemoveViewerDiscretionDialogPatch {
|
public class RemoveViewerDiscretionDialogPatch {
|
||||||
|
private static final String[] VIEWER_DISCRETION_DIALOG_PLAYABILITY_STATUS = {
|
||||||
|
"AGE_CHECK_REQUIRED",
|
||||||
|
"AGE_VERIFICATION_REQUIRED",
|
||||||
|
"CONTENT_CHECK_REQUIRED",
|
||||||
|
"LOGIN_REQUIRED"
|
||||||
|
};
|
||||||
|
@NonNull
|
||||||
|
private static volatile String playabilityStatus = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void confirmDialog(AlertDialog dialog) {
|
public static void confirmDialog(AlertDialog dialog) {
|
||||||
if (Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
|
// The dialog may already be shown due to the AlertDialog#create() method.
|
||||||
Logger.printDebug(() -> "Clicking alert dialog dismiss button");
|
// Call the AlertDialog#show() method only when the dialog is not shown.
|
||||||
|
if (!dialog.isShowing()) {
|
||||||
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
|
||||||
button.setSoundEffectsEnabled(false);
|
dialog.show();
|
||||||
button.performClick();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
|
if (shouldConfirmDialog()) {
|
||||||
Logger.printDebug(() -> "Showing alert dialog");
|
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
dialog.show();
|
if (button != null) {
|
||||||
|
Window window = dialog.getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
// Resize the dialog to 0 before clicking the button.
|
||||||
|
// If the dialog is not resized to 0, it will remain visible for about a second before closing.
|
||||||
|
WindowManager.LayoutParams params = window.getAttributes();
|
||||||
|
params.height = 0;
|
||||||
|
params.width = 0;
|
||||||
|
|
||||||
|
// Change the size of AlertDialog to 0.
|
||||||
|
window.setAttributes(params);
|
||||||
|
|
||||||
|
// Disable AlertDialog's background dim.
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||||
|
}
|
||||||
|
Logger.printDebug(() -> "Clicking alert dialog dismiss button");
|
||||||
|
button.callOnClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static AlertDialog confirmDialog(AlertDialog.Builder builder) {
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
confirmDialog(dialog);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Modern-style dialog is controlled by an obfuscated class and require additional hooking to get the buttons.
|
||||||
|
* Disabling the modern-style dialog is the simplest workaround.
|
||||||
|
* Since the purpose of the patch is to close the dialog immediately, this isn't a problem.
|
||||||
|
*
|
||||||
|
* @return Whether to use modern-style dialog.
|
||||||
|
* If false, AlertDialog is used.
|
||||||
|
*/
|
||||||
|
public static boolean disableModernDialog(boolean original) {
|
||||||
|
return !shouldConfirmDialog() && original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @param status Enum value of 'playabilityStatus.status' in '/player' endpoint responses.
|
||||||
|
*/
|
||||||
|
public static void setPlayabilityStatus(@Nullable Enum<?> status) {
|
||||||
|
playabilityStatus = status == null ? "" : status.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The viewer discretion dialog shows when the playability status is
|
||||||
|
* [AGE_CHECK_REQUIRED], [AGE_VERIFICATION_REQUIRED], [CONTENT_CHECK_REQUIRED], or [LOGIN_REQUIRED].
|
||||||
|
* Verify the playability status to prevent unintended dialog closures.
|
||||||
|
*
|
||||||
|
* @return Whether to close the dialog.
|
||||||
|
*/
|
||||||
|
private static boolean shouldConfirmDialog() {
|
||||||
|
return Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()
|
||||||
|
&& Utils.containsAny(playabilityStatus, VIEWER_DISCRETION_DIALOG_PLAYABILITY_STATUS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-17
@@ -49,21 +49,21 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The last litho based Shorts loaded.
|
* The last litho based Shorts loaded.
|
||||||
* May be the same value as {@link #currentVideoData}, but usually is the next short to swipe to.
|
* Maybe the same value as {@link #currentVideoData}, but usually is the next short to swipe to.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilter}
|
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilter}
|
||||||
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
* detects the video IDs, but the current Short can arbitrarily reload the same span,
|
||||||
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("ReturnYouTubeDislikePatch.class")
|
@GuardedBy("ReturnYouTubeDislikePatch.class")
|
||||||
private static int useLithoShortsVideoDataCount;
|
private static int useLithoShortsVideoDataCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row.
|
* Last video ID prefetched. Field is to prevent prefetching the same video ID multiple times in a row.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static volatile String lastPrefetchedVideoId;
|
private static volatile String lastPrefetchedVideoId;
|
||||||
@@ -98,6 +98,19 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
// Litho player for both regular videos and Shorts.
|
// Litho player for both regular videos and Shorts.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Logs if new litho text layout is used.
|
||||||
|
*/
|
||||||
|
public static boolean useNewLithoTextCreation(boolean useNewLithoTextCreation) {
|
||||||
|
// Don't force flag on/off unless debugging patch hooks,
|
||||||
|
// because forcing off with newer YT targets causes Shorts player to show no buttons,
|
||||||
|
// presumably because the old litho data isn't in the layout data.
|
||||||
|
Logger.printDebug(() -> "useNewLithoTextCreation: " + useNewLithoTextCreation);
|
||||||
|
return useNewLithoTextCreation;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
@@ -113,7 +126,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
* Called when a litho text component is initially created,
|
* Called when a litho text component is initially created,
|
||||||
* and also when a Span is later reused again (such as scrolling off/on screen).
|
* and also when a Span is later reused again (such as scrolling off/on screen).
|
||||||
*
|
*
|
||||||
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
|
* This method is sometimes called on the main thread, but it is usually called _off_ the main thread.
|
||||||
* This method can be called multiple times for the same UI element (including after dislikes was added).
|
* This method can be called multiple times for the same UI element (including after dislikes was added).
|
||||||
*
|
*
|
||||||
* @param original Original char sequence was created or reused by Litho.
|
* @param original Original char sequence was created or reused by Litho.
|
||||||
@@ -185,14 +198,14 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
|
|
||||||
final ReturnYouTubeDislike videoData;
|
final ReturnYouTubeDislike videoData;
|
||||||
if (decrementUseLithoDataIfNeeded()) {
|
if (decrementUseLithoDataIfNeeded()) {
|
||||||
// New Short is loading off screen.
|
// New Short is loading off-screen.
|
||||||
videoData = lastLithoShortsVideoData;
|
videoData = lastLithoShortsVideoData;
|
||||||
} else {
|
} else {
|
||||||
videoData = currentVideoData;
|
videoData = currentVideoData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoData == null) {
|
if (videoData == null) {
|
||||||
// The Shorts litho video id filter did not detect the video id.
|
// The Shorts litho video ID filter did not detect the video ID.
|
||||||
// This is normal in incognito mode, but otherwise is abnormal.
|
// This is normal in incognito mode, but otherwise is abnormal.
|
||||||
Logger.printDebug(() -> "Cannot modify Shorts litho span, data is null");
|
Logger.printDebug(() -> "Cannot modify Shorts litho span, data is null");
|
||||||
return original;
|
return original;
|
||||||
@@ -292,7 +305,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove Rolling Number text view modifications made by this patch.
|
* 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).
|
* Required as it appears text views can be reused for other rolling numbers (view count, upload time, etc.).
|
||||||
*/
|
*/
|
||||||
private static void removeRollingNumberPatchChanges(TextView view) {
|
private static void removeRollingNumberPatchChanges(TextView view) {
|
||||||
if (view.getCompoundDrawablePadding() != 0) {
|
if (view.getCompoundDrawablePadding() != 0) {
|
||||||
@@ -314,7 +327,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
// Called for all instances of RollingNumber, so must check if text is for a dislikes.
|
// 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.
|
// Text will already have the correct content, but it's missing the drawable separators.
|
||||||
if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(original.toString())) {
|
if (!ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(original.toString())) {
|
||||||
// The text is the video view count, upload time, or some other text.
|
// The text is the video view count, upload time, or some other text.
|
||||||
removeRollingNumberPatchChanges(view);
|
removeRollingNumberPatchChanges(view);
|
||||||
@@ -351,13 +364,13 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Video Id and voting hooks (all players).
|
// Video ID and voting hooks (all players).
|
||||||
//
|
//
|
||||||
|
|
||||||
private static volatile boolean lastPlayerResponseWasShort;
|
private static volatile boolean lastPlayerResponseWasShort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
* Injection point. Uses 'playback response' video ID hook to preload RYD.
|
||||||
*/
|
*/
|
||||||
public static void preloadVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
public static void preloadVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
try {
|
try {
|
||||||
@@ -387,7 +400,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (waitForFetchToComplete && !fetch.fetchCompleted()) {
|
if (waitForFetchToComplete && !fetch.fetchCompleted()) {
|
||||||
// This call is off the main thread, so wait until the RYD fetch completely finishes,
|
// This call is off the main thread, so wait until the RYD fetch completely finishes,
|
||||||
// otherwise if this returns before the fetch completes then the UI can
|
// otherwise if this returns before the fetch completes then the UI can
|
||||||
// become frozen when the main thread tries to modify the litho Shorts dislikes and
|
// become frozen when the main thread tries to modify the litho Shorts dislikes, and
|
||||||
// it must wait for the fetch.
|
// it must wait for the fetch.
|
||||||
// Only need to do this for the first Short opened, as the next Short to swipe to
|
// Only need to do this for the first Short opened, as the next Short to swipe to
|
||||||
// are preloaded in the background.
|
// are preloaded in the background.
|
||||||
@@ -406,7 +419,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Uses 'current playing' video id hook. Always called on main thread.
|
* Injection point. Uses 'current playing' video ID hook. Always called on main thread.
|
||||||
*/
|
*/
|
||||||
public static void newVideoLoaded(@NonNull String videoId) {
|
public static void newVideoLoaded(@NonNull String videoId) {
|
||||||
try {
|
try {
|
||||||
@@ -424,7 +437,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (videoIdIsSame(currentVideoData, videoId)) {
|
if (videoIdIsSame(currentVideoData, videoId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Logger.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType);
|
Logger.printDebug(() -> "New video ID: " + videoId + " playerType: " + currentPlayerType);
|
||||||
|
|
||||||
if (!Utils.isNetworkConnected()) {
|
if (!Utils.isNetworkConnected()) {
|
||||||
Logger.printDebug(() -> "Cannot fetch RYD, network is not connected");
|
Logger.printDebug(() -> "Cannot fetch RYD, network is not connected");
|
||||||
@@ -450,16 +463,16 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (videoId == null) {
|
if (videoId == null) {
|
||||||
// Litho filter did not detect the video id. App is in incognito mode,
|
// 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.
|
// 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
|
// Must clear both currently playing and last litho data otherwise the
|
||||||
// next regular video may use the wrong data.
|
// next regular video may use the wrong data.
|
||||||
Logger.printDebug(() -> "Litho filter did not find any video ids");
|
Logger.printDebug(() -> "Litho filter did not find any video IDs");
|
||||||
clearData();
|
clearData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "New litho Shorts video id: " + videoId);
|
Logger.printDebug(() -> "New litho Shorts video ID: " + videoId);
|
||||||
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
videoData.setVideoIdIsShort(true);
|
videoData.setVideoIdIsShort(true);
|
||||||
lastLithoShortsVideoData = videoData;
|
lastLithoShortsVideoData = videoData;
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class SeekbarThumbnailsPatch {
|
|
||||||
|
|
||||||
public static final class SeekbarThumbnailsHighQualityAvailability implements Setting.Availability {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return VersionCheckPatch.IS_19_17_OR_GREATER || !Settings.RESTORE_OLD_SEEKBAR_THUMBNAILS.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Setting<?>> getParentSettings() {
|
|
||||||
return List.of(Settings.RESTORE_OLD_SEEKBAR_THUMBNAILS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final boolean SEEKBAR_THUMBNAILS_HIGH_QUALITY_ENABLED
|
|
||||||
= Settings.SEEKBAR_THUMBNAILS_HIGH_QUALITY.get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean useHighQualityFullscreenThumbnails() {
|
|
||||||
return SEEKBAR_THUMBNAILS_HIGH_QUALITY_ENABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean useFullscreenSeekbarThumbnails() {
|
|
||||||
return !Settings.RESTORE_OLD_SEEKBAR_THUMBNAILS.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -109,7 +109,7 @@ public class ShortsAutoplayPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (original == null) {
|
if (original == null) {
|
||||||
// Cannot return null, as null is used to indicate Short was auto played.
|
// Cannot return null, as null is used to indicate the Short was autoplayed.
|
||||||
// Unpatched app replaces null with unknown enum type (appears to fix for bad api data).
|
// Unpatched app replaces null with unknown enum type (appears to fix for bad api data).
|
||||||
Enum<?> unknown = ShortsLoopBehavior.UNKNOWN.ytEnumValue;
|
Enum<?> unknown = ShortsLoopBehavior.UNKNOWN.ytEnumValue;
|
||||||
Logger.printDebug(() -> "Original is null, returning: " + unknown.name());
|
Logger.printDebug(() -> "Original is null, returning: " + unknown.name());
|
||||||
|
|||||||
+3
-3
@@ -3,8 +3,8 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class SeekbarTappingPatch {
|
public final class TapToSeekPatch {
|
||||||
public static boolean seekbarTappingEnabled() {
|
public static boolean tapToSeekEnabled() {
|
||||||
return Settings.SEEKBAR_TAPPING.get();
|
return Settings.TAP_TO_SEEK.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ToolbarPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hookToolbar(Enum<?> buttonEnum, ImageView imageView) {
|
||||||
|
final String enumString = buttonEnum.name();
|
||||||
|
if (enumString.isEmpty() ||
|
||||||
|
imageView == null ||
|
||||||
|
!(imageView.getParent() instanceof View view)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "enumString: " + enumString);
|
||||||
|
|
||||||
|
hookToolbar(enumString, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
private static void hookToolbar(String enumString, View parentView) {
|
||||||
|
// Code added by patch.
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -14,4 +14,13 @@ public class VideoAdsPatch {
|
|||||||
return SHOW_VIDEO_ADS;
|
return SHOW_VIDEO_ADS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String hideShortsAds(String osName) {
|
||||||
|
return SHOW_VIDEO_ADS
|
||||||
|
? osName
|
||||||
|
: "Android Automotive";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-31
@@ -3,15 +3,13 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.Event;
|
import app.revanced.extension.youtube.shared.Event;
|
||||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
import app.revanced.extension.youtube.shared.VideoState;
|
import app.revanced.extension.youtube.shared.VideoState;
|
||||||
|
|
||||||
@@ -32,11 +30,20 @@ public final class VideoInformation {
|
|||||||
*/
|
*/
|
||||||
public interface VideoQualityMenuInterface {
|
public interface VideoQualityMenuInterface {
|
||||||
// Method is added during patching.
|
// Method is added during patching.
|
||||||
void patch_setQuality(VideoQuality quality);
|
void patch_setQuality(VideoQualityInterface quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video resolution of the automatic quality option..
|
* Interface to use obfuscated methods.
|
||||||
|
*/
|
||||||
|
public interface VideoQualityInterface {
|
||||||
|
// Methods are added during patching.
|
||||||
|
String patch_getQualityName();
|
||||||
|
int patch_getResolution();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video resolution of the automatic quality option.
|
||||||
*/
|
*/
|
||||||
public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
|
public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
|
||||||
|
|
||||||
@@ -44,7 +51,7 @@ public final class VideoInformation {
|
|||||||
* Video quality names are the same text for all languages.
|
* Video quality names are the same text for all languages.
|
||||||
* Premium can be "1080p Premium" or "1080p60 Premium"
|
* Premium can be "1080p Premium" or "1080p60 Premium"
|
||||||
*/
|
*/
|
||||||
public static final String VIDEO_QUALITY_PREMIUM_NAME = "Premium";
|
private static final String VIDEO_QUALITY_PREMIUM_NAME = "Premium";
|
||||||
|
|
||||||
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
||||||
/**
|
/**
|
||||||
@@ -76,14 +83,14 @@ public final class VideoInformation {
|
|||||||
* The available qualities of the current video.
|
* The available qualities of the current video.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static VideoQuality[] currentQualities;
|
private static VideoQualityInterface[] currentQualities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current quality of the video playing.
|
* The current quality of the video playing.
|
||||||
* This is always the actual quality even if Automatic quality is active.
|
* This is always the actual quality even if Automatic quality is active.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static VideoQuality currentQuality;
|
private static VideoQualityInterface currentQuality;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current VideoQualityMenuInterface, set during setVideoQuality.
|
* The current VideoQualityMenuInterface, set during setVideoQuality.
|
||||||
@@ -94,15 +101,15 @@ public final class VideoInformation {
|
|||||||
/**
|
/**
|
||||||
* Callback for when the current quality changes.
|
* Callback for when the current quality changes.
|
||||||
*/
|
*/
|
||||||
public static final Event<VideoQuality> onQualityChange = new Event<>();
|
public static final Event<VideoQualityInterface> onQualityChange = new Event<>();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static VideoQuality[] getCurrentQualities() {
|
public static VideoQualityInterface[] getCurrentQualities() {
|
||||||
return currentQualities;
|
return currentQualities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static VideoQuality getCurrentQuality() {
|
public static VideoQualityInterface getCurrentQuality() {
|
||||||
return currentQuality;
|
return currentQuality;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +140,7 @@ public final class VideoInformation {
|
|||||||
*
|
*
|
||||||
* @param mdxPlayerDirector MDX player director object (casting mode).
|
* @param mdxPlayerDirector MDX player director object (casting mode).
|
||||||
*/
|
*/
|
||||||
public static void initializeMdx(@NonNull PlaybackController mdxPlayerDirector) {
|
public static void initializeMDX(@NonNull PlaybackController mdxPlayerDirector) {
|
||||||
try {
|
try {
|
||||||
mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector));
|
mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -144,11 +151,11 @@ public final class VideoInformation {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
* @param newlyLoadedVideoId id of the current video
|
* @param newlyLoadedVideoId ID of the current video
|
||||||
*/
|
*/
|
||||||
public static void setVideoId(@NonNull String newlyLoadedVideoId) {
|
public static void setVideoId(@NonNull String newlyLoadedVideoId) {
|
||||||
if (!videoId.equals(newlyLoadedVideoId)) {
|
if (!videoId.equals(newlyLoadedVideoId)) {
|
||||||
Logger.printDebug(() -> "New video id: " + newlyLoadedVideoId);
|
Logger.printDebug(() -> "New video ID: " + newlyLoadedVideoId);
|
||||||
videoId = newlyLoadedVideoId;
|
videoId = newlyLoadedVideoId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,11 +185,11 @@ public final class VideoInformation {
|
|||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
*
|
*
|
||||||
* @param videoId The id of the last video loaded.
|
* @param videoId The ID of the last video loaded.
|
||||||
*/
|
*/
|
||||||
public static void setPlayerResponseVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
public static void setPlayerResponseVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
if (!playerResponseVideoId.equals(videoId)) {
|
if (!playerResponseVideoId.equals(videoId)) {
|
||||||
Logger.printDebug(() -> "New player response video id: " + videoId);
|
Logger.printDebug(() -> "New player response video ID: " + videoId);
|
||||||
playerResponseVideoId = videoId;
|
playerResponseVideoId = videoId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +280,7 @@ public final class VideoInformation {
|
|||||||
// The difference has to be a different second mark in order to avoid infinite skip loops
|
// The difference has to be a different second mark in order to avoid infinite skip loops
|
||||||
// as the Lounge API only supports whole seconds.
|
// as the Lounge API only supports whole seconds.
|
||||||
if (adjustedSeekTime / 1000 == videoTime / 1000) {
|
if (adjustedSeekTime / 1000 == videoTime / 1000) {
|
||||||
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small "
|
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small"
|
||||||
+ "(" + (adjustedSeekTime - videoTime) + "ms)");
|
+ "(" + (adjustedSeekTime - videoTime) + "ms)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -301,7 +308,7 @@ public final class VideoInformation {
|
|||||||
Logger.printDebug(() -> "Seeking relative to: " + seekTime);
|
Logger.printDebug(() -> "Seeking relative to: " + seekTime);
|
||||||
|
|
||||||
// 19.39+ does not have a boolean return type for relative seek.
|
// 19.39+ does not have a boolean return type for relative seek.
|
||||||
// But can call both methods and it works correctly for both situations.
|
// But can call both methods, and it works correctly for both situations.
|
||||||
PlaybackController controller = playerControllerRef.get();
|
PlaybackController controller = playerControllerRef.get();
|
||||||
if (controller == null) {
|
if (controller == null) {
|
||||||
Logger.printDebug(() -> "Cannot seek relative as player controller is null");
|
Logger.printDebug(() -> "Cannot seek relative as player controller is null");
|
||||||
@@ -310,7 +317,7 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the fine adjustment function so it's at least 1 second before/after.
|
// Adjust the fine adjustment function so it's at least 1 second before/after.
|
||||||
// Otherwise the fine adjustment will do nothing when casting.
|
// Otherwise, the fine adjustment will do nothing when casting.
|
||||||
final long adjustedSeekTime;
|
final long adjustedSeekTime;
|
||||||
if (seekTime < 0) {
|
if (seekTime < 0) {
|
||||||
adjustedSeekTime = Math.min(seekTime, -1000);
|
adjustedSeekTime = Math.min(seekTime, -1000);
|
||||||
@@ -330,9 +337,9 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id of the last video opened. Includes Shorts.
|
* ID of the last video opened. Includes Shorts.
|
||||||
*
|
*
|
||||||
* @return The id of the video, or an empty string if no videos have been opened yet.
|
* @return The ID of the video, or an empty string if no videos have been opened yet.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getVideoId() {
|
public static String getVideoId() {
|
||||||
@@ -340,7 +347,7 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Differs from {@link #videoId} as this is the video id for the
|
* Differs from {@link #videoId} as this is the video ID for the
|
||||||
* last player response received, which may not be the last video opened.
|
* last player response received, which may not be the last video opened.
|
||||||
* <p>
|
* <p>
|
||||||
* If Shorts are loading the background, this commonly will be
|
* If Shorts are loading the background, this commonly will be
|
||||||
@@ -348,7 +355,7 @@ public final class VideoInformation {
|
|||||||
* <p>
|
* <p>
|
||||||
* For most use cases, you should instead use {@link #getVideoId()}.
|
* For most use cases, you should instead use {@link #getVideoId()}.
|
||||||
*
|
*
|
||||||
* @return The id of the last video loaded, or an empty string if no videos have been loaded yet.
|
* @return The ID of the last video loaded, or an empty string if no videos have been loaded yet.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getPlayerResponseVideoId() {
|
public static String getPlayerResponseVideoId() {
|
||||||
@@ -356,8 +363,8 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the last player response video id was a Short.
|
* @return If the last player response video ID was a Short.
|
||||||
* Includes Shorts shelf items appearing in the feed that are not opened.
|
* Include Shorts shelf items appearing in the feed that are not opened.
|
||||||
* @see #lastVideoIdIsShort()
|
* @see #lastVideoIdIsShort()
|
||||||
*/
|
*/
|
||||||
public static boolean lastPlayerResponseIsShort() {
|
public static boolean lastPlayerResponseIsShort() {
|
||||||
@@ -365,7 +372,7 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the last player response video id _that was opened_ was a Short.
|
* @return If the last player response video ID _that was opened_ was a Short.
|
||||||
*/
|
*/
|
||||||
public static boolean lastVideoIdIsShort() {
|
public static boolean lastVideoIdIsShort() {
|
||||||
return videoIdIsShort;
|
return videoIdIsShort;
|
||||||
@@ -450,7 +457,7 @@ public final class VideoInformation {
|
|||||||
qualityNeedsUpdating = true;
|
qualityNeedsUpdating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCurrentQuality(@Nullable VideoQuality quality) {
|
private static void setCurrentQuality(@Nullable VideoQualityInterface quality) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
if (currentQuality != quality) {
|
if (currentQuality != quality) {
|
||||||
Logger.printDebug(() -> "Current quality changed to: " + quality);
|
Logger.printDebug(() -> "Current quality changed to: " + quality);
|
||||||
@@ -462,7 +469,7 @@ public final class VideoInformation {
|
|||||||
/**
|
/**
|
||||||
* Forcefully changes the video quality of the currently playing video.
|
* Forcefully changes the video quality of the currently playing video.
|
||||||
*/
|
*/
|
||||||
public static void changeQuality(VideoQuality quality) {
|
public static void changeQuality(VideoQualityInterface quality) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
if (currentMenuInterface == null) {
|
if (currentMenuInterface == null) {
|
||||||
@@ -501,7 +508,7 @@ public final class VideoInformation {
|
|||||||
* @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2
|
* @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2
|
||||||
* @param originalQualityIndex quality index to use, as chosen by YouTube
|
* @param originalQualityIndex quality index to use, as chosen by YouTube
|
||||||
*/
|
*/
|
||||||
public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) {
|
public static int setVideoQuality(VideoQualityInterface[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) {
|
||||||
try {
|
try {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
currentMenuInterface = menu;
|
currentMenuInterface = menu;
|
||||||
@@ -516,7 +523,7 @@ public final class VideoInformation {
|
|||||||
// On extremely slow internet connections the index can initially be -1
|
// On extremely slow internet connections the index can initially be -1
|
||||||
originalQualityIndex = Math.max(0, originalQualityIndex);
|
originalQualityIndex = Math.max(0, originalQualityIndex);
|
||||||
|
|
||||||
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
VideoQualityInterface updatedCurrentQuality = qualities[originalQualityIndex];
|
||||||
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
|
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
|
||||||
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
|
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
|
||||||
setCurrentQuality(updatedCurrentQuality);
|
setCurrentQuality(updatedCurrentQuality);
|
||||||
@@ -538,7 +545,7 @@ public final class VideoInformation {
|
|||||||
// Find the highest quality that is equal to or less than the preferred.
|
// Find the highest quality that is equal to or less than the preferred.
|
||||||
int i = 0;
|
int i = 0;
|
||||||
final int lastQualityIndex = qualities.length - 1;
|
final int lastQualityIndex = qualities.length - 1;
|
||||||
for (VideoQuality quality : qualities) {
|
for (VideoQualityInterface quality : qualities) {
|
||||||
final int qualityResolution = quality.patch_getResolution();
|
final int qualityResolution = quality.patch_getResolution();
|
||||||
if ((qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality)
|
if ((qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality)
|
||||||
// Use the lowest video quality if the default is lower than all available.
|
// Use the lowest video quality if the default is lower than all available.
|
||||||
@@ -572,4 +579,9 @@ public final class VideoInformation {
|
|||||||
}
|
}
|
||||||
return originalQualityIndex;
|
return originalQualityIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPremiumVideoQuality(@NonNull VideoQualityInterface quality) {
|
||||||
|
String qualityName = quality.patch_getQualityName();
|
||||||
|
return qualityName != null && qualityName.contains(VIDEO_QUALITY_PREMIUM_NAME);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.shared.ui.Dim;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class WideSearchbarPatch {
|
|
||||||
|
|
||||||
private static final Boolean WIDE_SEARCHBAR_ENABLED = Settings.WIDE_SEARCHBAR.get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean enableWideSearchbar(boolean original) {
|
|
||||||
return WIDE_SEARCHBAR_ENABLED || original;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void setActionBar(View view) {
|
|
||||||
try {
|
|
||||||
if (!WIDE_SEARCHBAR_ENABLED) return;
|
|
||||||
|
|
||||||
View searchBarView = Utils.getChildViewByResourceName(view, "search_bar");
|
|
||||||
|
|
||||||
final int paddingLeft = searchBarView.getPaddingLeft();
|
|
||||||
final int paddingRight = searchBarView.getPaddingRight();
|
|
||||||
final int paddingTop = searchBarView.getPaddingTop();
|
|
||||||
final int paddingBottom = searchBarView.getPaddingBottom();
|
|
||||||
final int paddingStart = Dim.dp8;
|
|
||||||
|
|
||||||
if (Utils.isRightToLeftLocale()) {
|
|
||||||
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
|
|
||||||
} else {
|
|
||||||
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "setActionBar failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-5
@@ -36,7 +36,7 @@ public final class AnnouncementsPatch {
|
|||||||
HttpURLConnection connection =
|
HttpURLConnection connection =
|
||||||
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
||||||
|
|
||||||
Logger.printDebug(() -> "Get latest announcement IDs route connection url: " + connection.getURL());
|
Logger.printDebug(() -> "Get latest announcement IDs route connection URL: " + connection.getURL());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Do not show the announcement if the request failed.
|
// Do not show the announcement if the request failed.
|
||||||
@@ -59,10 +59,10 @@ public final class AnnouncementsPatch {
|
|||||||
// Parse the ID. Fall-back to raw string if it fails.
|
// Parse the ID. Fall-back to raw string if it fails.
|
||||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
try {
|
try {
|
||||||
final var announcementIds = new JSONArray(jsonString);
|
final var announcementIDs = new JSONArray(jsonString);
|
||||||
if (announcementIds.length() == 0) return true;
|
if (announcementIDs.length() == 0) return true;
|
||||||
|
|
||||||
id = announcementIds.getJSONObject(0).getInt("id");
|
id = announcementIDs.getJSONObject(0).getInt("id");
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ public final class AnnouncementsPatch {
|
|||||||
HttpURLConnection connection = AnnouncementsRoutes
|
HttpURLConnection connection = AnnouncementsRoutes
|
||||||
.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENTS);
|
.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENTS);
|
||||||
|
|
||||||
Logger.printDebug(() -> "Get latest announcements route connection url: " + connection.getURL());
|
Logger.printDebug(() -> "Get latest announcements route connection URL: " + connection.getURL());
|
||||||
|
|
||||||
var jsonString = Requester.parseStringAndDisconnect(connection);
|
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||||
|
|
||||||
|
|||||||
+129
-83
@@ -2,9 +2,13 @@ package app.revanced.extension.youtube.patches.litho;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
import android.app.Dialog;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -19,12 +23,18 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class AdsFilter extends Filter {
|
public final class AdsFilter extends Filter {
|
||||||
// region Fullscreen ad
|
// region Fullscreen ad
|
||||||
private static volatile long lastTimeClosedFullscreenAd;
|
private static final ByteArrayFilterGroup fullscreenAd = new ByteArrayFilterGroup(
|
||||||
private static final Instrumentation instrumentation = new Instrumentation();
|
null,
|
||||||
private final StringFilterGroup fullscreenAd;
|
"_interstitial"
|
||||||
|
);
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
private static final String[] PLAYER_POPUP_AD_PANEL_IDS = {
|
||||||
|
"PAproduct", // Shopping.
|
||||||
|
"jumpahead" // Premium promotion.
|
||||||
|
};
|
||||||
|
|
||||||
// https://encrypted-tbn0.gstatic.com/shopping?q=abc
|
// https://encrypted-tbn0.gstatic.com/shopping?q=abc
|
||||||
private static final String STORE_BANNER_DOMAIN = "gstatic.com/shopping";
|
private static final String STORE_BANNER_DOMAIN = "gstatic.com/shopping";
|
||||||
private static final boolean HIDE_END_SCREEN_STORE_BANNER =
|
private static final boolean HIDE_END_SCREEN_STORE_BANNER =
|
||||||
@@ -32,9 +42,10 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
|
|
||||||
private final StringFilterGroup playerShoppingShelf;
|
private final StringFilterGroup promotionBanner;
|
||||||
private final ByteArrayFilterGroup playerShoppingShelfBuffer;
|
private final ByteArrayFilterGroup promotionBannerBuffer;
|
||||||
|
private final StringFilterGroup buyMovieAd;
|
||||||
|
private final ByteArrayFilterGroup buyMovieAdBuffer;
|
||||||
|
|
||||||
public AdsFilter() {
|
public AdsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@@ -47,7 +58,6 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
// Identifiers.
|
// Identifiers.
|
||||||
|
|
||||||
|
|
||||||
final var carouselAd = new StringFilterGroup(
|
final var carouselAd = new StringFilterGroup(
|
||||||
Settings.HIDE_GENERAL_ADS,
|
Settings.HIDE_GENERAL_ADS,
|
||||||
"carousel_ad"
|
"carousel_ad"
|
||||||
@@ -56,11 +66,6 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
// Paths.
|
// Paths.
|
||||||
|
|
||||||
fullscreenAd = new StringFilterGroup(
|
|
||||||
Settings.HIDE_FULLSCREEN_ADS,
|
|
||||||
"_interstitial"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var generalAds = new StringFilterGroup(
|
final var generalAds = new StringFilterGroup(
|
||||||
Settings.HIDE_GENERAL_ADS,
|
Settings.HIDE_GENERAL_ADS,
|
||||||
"_ad_with",
|
"_ad_with",
|
||||||
@@ -79,11 +84,11 @@ public final class AdsFilter extends Filter {
|
|||||||
"hero_promo_image",
|
"hero_promo_image",
|
||||||
// text_image_button_group_layout, landscape_image_button_group_layout, full_width_square_image_button_group_layout
|
// text_image_button_group_layout, landscape_image_button_group_layout, full_width_square_image_button_group_layout
|
||||||
"image_button_group_layout",
|
"image_button_group_layout",
|
||||||
|
"landscape_image_carousel_layout",
|
||||||
"landscape_image_wide_button_layout",
|
"landscape_image_wide_button_layout",
|
||||||
"primetime_promo",
|
"primetime_promo",
|
||||||
"product_details",
|
"product_details",
|
||||||
"square_image_layout",
|
"square_image_layout",
|
||||||
"statement_banner",
|
|
||||||
"text_image_button_layout",
|
"text_image_button_layout",
|
||||||
"text_image_no_button_layout", // Tablet layout search results.
|
"text_image_no_button_layout", // Tablet layout search results.
|
||||||
"video_display_button_group_layout",
|
"video_display_button_group_layout",
|
||||||
@@ -91,7 +96,8 @@ public final class AdsFilter extends Filter {
|
|||||||
"video_display_carousel_buttoned_short_dr_layout",
|
"video_display_carousel_buttoned_short_dr_layout",
|
||||||
"video_display_full_buttoned_short_dr_layout",
|
"video_display_full_buttoned_short_dr_layout",
|
||||||
"video_display_full_layout",
|
"video_display_full_layout",
|
||||||
"watch_metadata_app_promo"
|
"watch_metadata_app_promo",
|
||||||
|
"shopping_timely_shelf." // Injection point below hides the empty space.
|
||||||
);
|
);
|
||||||
|
|
||||||
final var movieAds = new StringFilterGroup(
|
final var movieAds = new StringFilterGroup(
|
||||||
@@ -104,6 +110,16 @@ public final class AdsFilter extends Filter {
|
|||||||
"offer_module_root"
|
"offer_module_root"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
buyMovieAd = new StringFilterGroup(
|
||||||
|
Settings.HIDE_MOVIES_SECTION,
|
||||||
|
"video_lockup_with_attachment.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
buyMovieAdBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"FEstorefront"
|
||||||
|
);
|
||||||
|
|
||||||
final var viewProducts = new StringFilterGroup(
|
final var viewProducts = new StringFilterGroup(
|
||||||
Settings.HIDE_VIEW_PRODUCTS_BANNER,
|
Settings.HIDE_VIEW_PRODUCTS_BANNER,
|
||||||
"product_item",
|
"product_item",
|
||||||
@@ -116,64 +132,126 @@ public final class AdsFilter extends Filter {
|
|||||||
"shopping_description_shelf.e"
|
"shopping_description_shelf.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
playerShoppingShelf = new StringFilterGroup(
|
|
||||||
Settings.HIDE_CREATOR_STORE_SHELF,
|
|
||||||
"horizontal_shelf.e"
|
|
||||||
);
|
|
||||||
|
|
||||||
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
|
||||||
null,
|
|
||||||
"shopping_item_card_list"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var webLinkPanel = new StringFilterGroup(
|
|
||||||
Settings.HIDE_WEB_SEARCH_RESULTS,
|
|
||||||
"web_link_panel"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var merchandise = new StringFilterGroup(
|
final var merchandise = new StringFilterGroup(
|
||||||
Settings.HIDE_MERCHANDISE_BANNERS,
|
Settings.HIDE_MERCHANDISE_BANNERS,
|
||||||
"product_carousel",
|
"product_carousel",
|
||||||
"shopping_carousel.e" // Channel profile shopping shelf.
|
"shopping_carousel.e" // Channel profile shopping shelf.
|
||||||
);
|
);
|
||||||
|
|
||||||
|
promotionBanner = new StringFilterGroup(
|
||||||
|
Settings.HIDE_YOUTUBE_PREMIUM_PROMOTIONS,
|
||||||
|
"statement_banner"
|
||||||
|
);
|
||||||
|
|
||||||
|
promotionBannerBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"img/promos/growth/", // Link, https://www.gstatic.com/youtube/img/promos/growth/ is only used for ads.
|
||||||
|
"SPunlimited" // Word associated with Premium, should be unique to differentiate Doodle from ad banner.
|
||||||
|
);
|
||||||
|
|
||||||
final var selfSponsor = new StringFilterGroup(
|
final var selfSponsor = new StringFilterGroup(
|
||||||
Settings.HIDE_SELF_SPONSOR,
|
Settings.HIDE_SELF_SPONSOR,
|
||||||
"cta_shelf_card"
|
"cta_shelf_card"
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
fullscreenAd,
|
buyMovieAd,
|
||||||
generalAds,
|
generalAds,
|
||||||
merchandise,
|
merchandise,
|
||||||
movieAds,
|
movieAds,
|
||||||
playerShoppingShelf,
|
promotionBanner,
|
||||||
selfSponsor,
|
selfSponsor,
|
||||||
shoppingLinks,
|
shoppingLinks,
|
||||||
viewProducts,
|
viewProducts
|
||||||
webLinkPanel
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == playerShoppingShelf) {
|
if (matchedGroup == buyMovieAd) {
|
||||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
return contentIndex == 0 && buyMovieAdBuffer.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path)) {
|
if (matchedGroup == promotionBanner) {
|
||||||
return false;
|
return contentIndex == 0 && promotionBannerBuffer.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == fullscreenAd) {
|
return !exceptions.matches(path);
|
||||||
if (path.contains("|ImageType|")) closeFullscreenAd();
|
}
|
||||||
|
|
||||||
// Do not actually filter the fullscreen ad otherwise it will leave a dimmed screen.
|
/**
|
||||||
return false;
|
* Injection point.
|
||||||
|
* Called from a different place then the other filters.
|
||||||
|
*/
|
||||||
|
public static void closeFullscreenAd(Object customDialog, @Nullable byte[] buffer) {
|
||||||
|
try {
|
||||||
|
if (!Settings.HIDE_FULLSCREEN_ADS.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null) {
|
||||||
|
Logger.printDebug(() -> "buffer is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullscreenAd.check(buffer).isFiltered() &&
|
||||||
|
customDialog instanceof Dialog dialog) {
|
||||||
|
Logger.printDebug(() -> "Closing fullscreen ad");
|
||||||
|
|
||||||
|
Window window = dialog.getWindow();
|
||||||
|
|
||||||
|
if (window != null) {
|
||||||
|
// Set the dialog size to 0 before closing
|
||||||
|
// If the dialog is not resized to 0, it will remain visible for about a second before closing
|
||||||
|
WindowManager.LayoutParams params = window.getAttributes();
|
||||||
|
params.height = 0;
|
||||||
|
params.width = 0;
|
||||||
|
|
||||||
|
// Change the size of dialog to 0
|
||||||
|
window.setAttributes(params);
|
||||||
|
|
||||||
|
// Disable dialog's background dim
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||||
|
|
||||||
|
// Restore window flags
|
||||||
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED);
|
||||||
|
|
||||||
|
// Restore decorView visibility
|
||||||
|
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss dialog
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "closeFullscreenAd failure", ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean hideAds() {
|
||||||
|
return Settings.HIDE_GENERAL_ADS.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String hideAds(String osName) {
|
||||||
|
return Settings.HIDE_GENERAL_ADS.get()
|
||||||
|
? "Android Automotive"
|
||||||
|
: osName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the view, which shows ads in the homepage.
|
||||||
|
*
|
||||||
|
* @param view The view, which shows ads.
|
||||||
|
*/
|
||||||
|
public static void hideAdAttributionView(View view) {
|
||||||
|
Utils.hideViewBy0dpUnderCondition(Settings.HIDE_GENERAL_ADS, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,50 +269,18 @@ public final class AdsFilter extends Filter {
|
|||||||
elementsList.add(protobufList);
|
elementsList.add(protobufList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the view, which shows ads in the homepage.
|
* Injection point.
|
||||||
*
|
|
||||||
* @param view The view, which shows ads.
|
|
||||||
*/
|
*/
|
||||||
public static void hideAdAttributionView(View view) {
|
public static boolean hideGetPremiumView() {
|
||||||
Utils.hideViewBy0dpUnderCondition(Settings.HIDE_GENERAL_ADS, view);
|
return Settings.HIDE_YOUTUBE_PREMIUM_PROMOTIONS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the fullscreen ad.
|
* Injection point.
|
||||||
* <p>
|
|
||||||
* The strategy is to send a back button event to the app to close the fullscreen ad using the back button event.
|
|
||||||
*/
|
*/
|
||||||
private static void closeFullscreenAd() {
|
public static boolean hidePlayerPopupAds(String panelId) {
|
||||||
final var currentTime = System.currentTimeMillis();
|
return Settings.HIDE_PLAYER_POPUP_ADS.get()
|
||||||
|
&& Utils.containsAny(panelId, PLAYER_POPUP_AD_PANEL_IDS);
|
||||||
// Prevent spamming the back button.
|
|
||||||
if (currentTime - lastTimeClosedFullscreenAd < 10000) return;
|
|
||||||
lastTimeClosedFullscreenAd = currentTime;
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Closing fullscreen ad");
|
|
||||||
|
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
|
||||||
// Must run off main thread (Odd, but whatever).
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
|
||||||
try {
|
|
||||||
instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// Injecting user events on Android 10+ requires the manifest to include
|
|
||||||
// INJECT_EVENTS, and it's usage is heavily restricted
|
|
||||||
// and requires the user to manually approve the permission in the device settings.
|
|
||||||
//
|
|
||||||
// And no matter what, permissions cannot be added for root installations
|
|
||||||
// as manifest changes are ignored for mount installations.
|
|
||||||
//
|
|
||||||
// Instead, catch the SecurityException and turn off hide full screen ads
|
|
||||||
// since this functionality does not work for these devices.
|
|
||||||
Logger.printInfo(() -> "Could not inject back button event", ex);
|
|
||||||
Settings.HIDE_FULLSCREEN_ADS.save(false);
|
|
||||||
Utils.showToastLong(str("revanced_hide_fullscreen_ads_feature_not_available_toast"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
isVideoQualityMenuVisible = true;
|
isVideoQualityMenuVisible = true;
|
||||||
|
|
||||||
|
|||||||
-154
@@ -1,154 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches.litho;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.litho.Filter;
|
|
||||||
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class ButtonsFilter extends Filter {
|
|
||||||
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e";
|
|
||||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e";
|
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e";
|
|
||||||
/**
|
|
||||||
* Video bar path when the video information is collapsed. Seems to shown only with 20.14+
|
|
||||||
*/
|
|
||||||
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.e";
|
|
||||||
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
|
||||||
|
|
||||||
private final StringFilterGroup likeSubscribeGlow;
|
|
||||||
private final StringFilterGroup actionBarGroup;
|
|
||||||
private final StringFilterGroup bufferFilterPathGroup;
|
|
||||||
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
|
||||||
|
|
||||||
public ButtonsFilter() {
|
|
||||||
actionBarGroup = new StringFilterGroup(
|
|
||||||
null,
|
|
||||||
VIDEO_ACTION_BAR_PATH
|
|
||||||
);
|
|
||||||
addIdentifierCallbacks(actionBarGroup);
|
|
||||||
|
|
||||||
|
|
||||||
likeSubscribeGlow = new StringFilterGroup(
|
|
||||||
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
|
|
||||||
"animated_button_border.e"
|
|
||||||
);
|
|
||||||
|
|
||||||
bufferFilterPathGroup = new StringFilterGroup(
|
|
||||||
null,
|
|
||||||
"|ContainerType|button.e"
|
|
||||||
);
|
|
||||||
|
|
||||||
addPathCallbacks(
|
|
||||||
likeSubscribeGlow,
|
|
||||||
new StringFilterGroup(
|
|
||||||
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
|
||||||
"|segmented_like_dislike_button"
|
|
||||||
),
|
|
||||||
new StringFilterGroup(
|
|
||||||
Settings.HIDE_DOWNLOAD_BUTTON,
|
|
||||||
"|download_button.e"
|
|
||||||
),
|
|
||||||
new StringFilterGroup(
|
|
||||||
Settings.HIDE_SAVE_BUTTON,
|
|
||||||
"|save_to_playlist_button"
|
|
||||||
),
|
|
||||||
new StringFilterGroup(
|
|
||||||
Settings.HIDE_CLIP_BUTTON,
|
|
||||||
"|clip_button.e"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: 20.22+ filtering of the action buttons doesn't work because
|
|
||||||
// the buffer is the same for all buttons.
|
|
||||||
if (!VersionCheckPatch.IS_20_22_OR_GREATER) {
|
|
||||||
addPathCallbacks(bufferFilterPathGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferButtonsGroupList.addAll(
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_REPORT_BUTTON,
|
|
||||||
"yt_outline_flag"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_SHARE_BUTTON,
|
|
||||||
"yt_outline_share"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_REMIX_BUTTON,
|
|
||||||
"yt_outline_youtube_shorts_plus"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_THANKS_BUTTON,
|
|
||||||
"yt_outline_dollar_sign_heart"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_ASK_BUTTON,
|
|
||||||
"yt_fill_spark"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_SHOP_BUTTON,
|
|
||||||
"yt_outline_bag"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_STOP_ADS_BUTTON,
|
|
||||||
"yt_outline_slash_circle_left"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_COMMENTS_BUTTON,
|
|
||||||
"yt_outline_message_bubble_right"
|
|
||||||
),
|
|
||||||
// Check for clip button both here and using a path filter,
|
|
||||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_CLIP_BUTTON,
|
|
||||||
"yt_outline_scissors"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_HYPE_BUTTON,
|
|
||||||
"yt_outline_star_shooting"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_PROMOTE_BUTTON,
|
|
||||||
"yt_outline_megaphone"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEveryFilterGroupEnabled() {
|
|
||||||
for (var group : pathCallbacks) {
|
|
||||||
if (!group.isEnabled()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var group : bufferButtonsGroupList) {
|
|
||||||
if (!group.isEnabled()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
|
||||||
if (matchedGroup == likeSubscribeGlow) {
|
|
||||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
|
||||||
&& path.contains(ANIMATED_VECTOR_TYPE_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the current matched group is the action bar group,
|
|
||||||
// in case every filter group is enabled, hide the action bar.
|
|
||||||
if (matchedGroup == actionBarGroup) {
|
|
||||||
return isEveryFilterGroupEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedGroup == bufferFilterPathGroup) {
|
|
||||||
// Make sure the current path is the right one to avoid false positives.
|
|
||||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH) || path.startsWith(COMPACTIFY_VIDEO_ACTION_BAR_PATH))
|
|
||||||
&& bufferButtonsGroupList.check(buffer).isFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-2
@@ -9,9 +9,11 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
public final class CommentsFilter extends Filter {
|
public final class CommentsFilter extends Filter {
|
||||||
|
|
||||||
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
|
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
|
||||||
|
private static final String VIDEO_LOCKUP_WITH_ATTACHMENT_PATH = "video_lockup_with_attachment.e";
|
||||||
|
|
||||||
private final StringFilterGroup chipBar;
|
private final StringFilterGroup chipBar;
|
||||||
private final ByteArrayFilterGroup aiCommentsSummary;
|
private final ByteArrayFilterGroup aiCommentsSummary;
|
||||||
|
private final StringFilterGroup comments;
|
||||||
private final StringFilterGroup emojiAndTimestampButtons;
|
private final StringFilterGroup emojiAndTimestampButtons;
|
||||||
|
|
||||||
public CommentsFilter() {
|
public CommentsFilter() {
|
||||||
@@ -41,7 +43,7 @@ public final class CommentsFilter extends Filter {
|
|||||||
"sponsorships_comments_footer.e"
|
"sponsorships_comments_footer.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
var comments = new StringFilterGroup(
|
comments = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMENTS_SECTION,
|
Settings.HIDE_COMMENTS_SECTION,
|
||||||
"video_metadata_carousel",
|
"video_metadata_carousel",
|
||||||
"_comments"
|
"_comments"
|
||||||
@@ -90,7 +92,7 @@ public final class CommentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == chipBar) {
|
if (matchedGroup == chipBar) {
|
||||||
// Playlist sort button uses same components and must only filter if the player is opened.
|
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||||
@@ -98,6 +100,13 @@ public final class CommentsFilter extends Filter {
|
|||||||
&& aiCommentsSummary.check(buffer).isFiltered();
|
&& aiCommentsSummary.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == comments) {
|
||||||
|
if (path.startsWith(VIDEO_LOCKUP_WITH_ATTACHMENT_PATH)) {
|
||||||
|
return Settings.HIDE_COMMENTS_SECTION_IN_HOME_FEED.get();
|
||||||
|
}
|
||||||
|
return Settings.HIDE_COMMENTS_SECTION.get();
|
||||||
|
}
|
||||||
|
|
||||||
if (matchedGroup == emojiAndTimestampButtons) {
|
if (matchedGroup == emojiAndTimestampButtons) {
|
||||||
return path.startsWith(COMMENT_COMPOSER_PATH);
|
return path.startsWith(COMMENT_COMPOSER_PATH);
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-50
@@ -1,10 +1,10 @@
|
|||||||
package app.revanced.extension.youtube.patches.litho;
|
package app.revanced.extension.youtube.patches.litho;
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.litho.Filter;
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.shared.EngagementPanel;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -12,26 +12,16 @@ public final class DescriptionComponentsFilter extends Filter {
|
|||||||
|
|
||||||
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
|
||||||
private final StringFilterGroup macroMarkersCarousel;
|
private final StringFilterGroup macroMarkersCarousel;
|
||||||
private final ByteArrayFilterGroupList macroMarkersCarouselGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList macroMarkersCarouselGroupList = new ByteArrayFilterGroupList();
|
||||||
private final StringFilterGroup horizontalShelf;
|
private final StringFilterGroup playlistSection;
|
||||||
private final ByteArrayFilterGroup cellVideoAttribute;
|
private final ByteArrayFilterGroupList playlistSectionGroupList = new ByteArrayFilterGroupList();
|
||||||
private final StringFilterGroup infoCardsSection;
|
private final StringFilterGroup featuredLinksSection;
|
||||||
|
private final StringFilterGroup featuredVideosSection;
|
||||||
private final StringFilterGroup subscribeButton;
|
private final StringFilterGroup subscribeButton;
|
||||||
private final StringFilterGroup aiGeneratedVideoSummarySection;
|
|
||||||
private final StringFilterGroup hypePoints;
|
|
||||||
|
|
||||||
public DescriptionComponentsFilter() {
|
public DescriptionComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
final StringFilterGroup aiGeneratedVideoSummarySection = new StringFilterGroup(
|
||||||
"compact_channel",
|
|
||||||
"description",
|
|
||||||
"grid_video",
|
|
||||||
"inline_expander",
|
|
||||||
"metadata"
|
|
||||||
);
|
|
||||||
|
|
||||||
aiGeneratedVideoSummarySection = new StringFilterGroup(
|
|
||||||
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
||||||
"cell_expandable_metadata.e"
|
"cell_expandable_metadata.e"
|
||||||
);
|
);
|
||||||
@@ -47,19 +37,33 @@ public final class DescriptionComponentsFilter extends Filter {
|
|||||||
"video_attributes_section"
|
"video_attributes_section"
|
||||||
);
|
);
|
||||||
|
|
||||||
final StringFilterGroup featuredLinksSection = new StringFilterGroup(
|
featuredLinksSection = new StringFilterGroup(
|
||||||
Settings.HIDE_FEATURED_LINKS_SECTION,
|
Settings.HIDE_FEATURED_LINKS_SECTION,
|
||||||
"media_lockup"
|
"media_lockup"
|
||||||
);
|
);
|
||||||
|
|
||||||
final StringFilterGroup featuredVideosSection = new StringFilterGroup(
|
featuredVideosSection = new StringFilterGroup(
|
||||||
Settings.HIDE_FEATURED_VIDEOS_SECTION,
|
Settings.HIDE_FEATURED_VIDEOS_SECTION,
|
||||||
"structured_description_video_lockup"
|
"structured_description_video_lockup"
|
||||||
);
|
);
|
||||||
|
|
||||||
final StringFilterGroup podcastSection = new StringFilterGroup(
|
playlistSection = new StringFilterGroup(
|
||||||
Settings.HIDE_PODCAST_SECTION,
|
// YT v20.14.43 doesn't use any buffer for Courses and Podcasts.
|
||||||
"playlist_section"
|
// So this component is also needed.
|
||||||
|
null,
|
||||||
|
"playlist_section.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
playlistSectionGroupList.addAll(
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_EXPLORE_COURSE_SECTION,
|
||||||
|
"yt_outline_creator_academy", // For Disable bold icons.
|
||||||
|
"yt_outline_experimental_graduation_cap"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_EXPLORE_PODCAST_SECTION,
|
||||||
|
"FEpodcasts_destination"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final StringFilterGroup transcriptSection = new StringFilterGroup(
|
final StringFilterGroup transcriptSection = new StringFilterGroup(
|
||||||
@@ -72,12 +76,17 @@ public final class DescriptionComponentsFilter extends Filter {
|
|||||||
"how_this_was_made_section"
|
"how_this_was_made_section"
|
||||||
);
|
);
|
||||||
|
|
||||||
hypePoints = new StringFilterGroup(
|
final StringFilterGroup courseProgressSection = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COURSE_PROGRESS_SECTION,
|
||||||
|
"course_progress"
|
||||||
|
);
|
||||||
|
|
||||||
|
final StringFilterGroup hypePoints = new StringFilterGroup(
|
||||||
Settings.HIDE_HYPE_POINTS,
|
Settings.HIDE_HYPE_POINTS,
|
||||||
"hype_points_factoid"
|
"hype_points_factoid"
|
||||||
);
|
);
|
||||||
|
|
||||||
infoCardsSection = new StringFilterGroup(
|
final StringFilterGroup infoCardsSection = new StringFilterGroup(
|
||||||
Settings.HIDE_INFO_CARDS_SECTION,
|
Settings.HIDE_INFO_CARDS_SECTION,
|
||||||
INFOCARDS_SECTION_PATH
|
INFOCARDS_SECTION_PATH
|
||||||
);
|
);
|
||||||
@@ -95,64 +104,72 @@ public final class DescriptionComponentsFilter extends Filter {
|
|||||||
macroMarkersCarouselGroupList.addAll(
|
macroMarkersCarouselGroupList.addAll(
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_CHAPTERS_SECTION,
|
Settings.HIDE_CHAPTERS_SECTION,
|
||||||
"chapters_horizontal_shelf"
|
"chapters_horizontal_shelf",
|
||||||
|
"auto-chapters",
|
||||||
|
"description-chapters"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_KEY_CONCEPTS_SECTION,
|
Settings.HIDE_KEY_CONCEPTS_SECTION,
|
||||||
"learning_concept_macro_markers_carousel_shelf"
|
"learning_concept_macro_markers_carousel_shelf",
|
||||||
|
"learning-concept"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
horizontalShelf = new StringFilterGroup(
|
|
||||||
Settings.HIDE_ATTRIBUTES_SECTION,
|
|
||||||
"horizontal_shelf.e"
|
|
||||||
);
|
|
||||||
|
|
||||||
cellVideoAttribute = new ByteArrayFilterGroup(
|
|
||||||
null,
|
|
||||||
"cell_video_attribute"
|
|
||||||
);
|
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
aiGeneratedVideoSummarySection,
|
aiGeneratedVideoSummarySection,
|
||||||
askSection,
|
askSection,
|
||||||
attributesSection,
|
courseProgressSection,
|
||||||
featuredLinksSection,
|
featuredLinksSection,
|
||||||
featuredVideosSection,
|
featuredVideosSection,
|
||||||
horizontalShelf,
|
|
||||||
howThisWasMadeSection,
|
howThisWasMadeSection,
|
||||||
hypePoints,
|
hypePoints,
|
||||||
infoCardsSection,
|
infoCardsSection,
|
||||||
macroMarkersCarousel,
|
macroMarkersCarousel,
|
||||||
podcastSection,
|
playlistSection,
|
||||||
subscribeButton,
|
subscribeButton,
|
||||||
transcriptSection
|
transcriptSection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
// The description panel can be opened in both the regular player and Shorts.
|
||||||
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
|
// If the description panel is opened in a Shorts, PlayerType is 'HIDDEN',
|
||||||
// Only hide if player is open, in case this component is used somewhere else.
|
// so 'PlayerType.getCurrent().isMaximizedOrFullscreen()' does not guarantee that the description panel is open.
|
||||||
return PlayerType.getCurrent().isMaximizedOrFullscreen();
|
// Instead, use the engagement id to check if the description panel is opened.
|
||||||
|
if (!EngagementPanel.isDescription()
|
||||||
|
// The user can minimize the player while the engagement panel is open.
|
||||||
|
//
|
||||||
|
// In this case, the engagement panel is treated as open.
|
||||||
|
// (If the player is dismissed, the engagement panel is considered closed)
|
||||||
|
//
|
||||||
|
// Therefore, the following exceptions can occur:
|
||||||
|
// 1. The user opened a regular video and opened the description panel.
|
||||||
|
// 2. The 'horizontalShelf' elements were hidden.
|
||||||
|
// 3. The user minimized the player.
|
||||||
|
// 4. The user manually refreshed the library tab without dismissing the player.
|
||||||
|
// 5. Since the engagement panel is treated as open, the history shelf is filtered.
|
||||||
|
//
|
||||||
|
// To handle these exceptions, filtering is not performed even when the player is minimized.
|
||||||
|
|| PlayerType.getCurrent() == PlayerType.WATCH_WHILE_MINIMIZED
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == subscribeButton) {
|
if (matchedGroup == featuredLinksSection || matchedGroup == featuredVideosSection || matchedGroup == subscribeButton) {
|
||||||
return path.startsWith(INFOCARDS_SECTION_PATH);
|
return path.startsWith(INFOCARDS_SECTION_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path)) return false;
|
if (matchedGroup == playlistSection) {
|
||||||
|
if (contentIndex != 0) return false;
|
||||||
|
return Settings.HIDE_EXPLORE_SECTION.get() || playlistSectionGroupList.check(buffer).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
if (matchedGroup == macroMarkersCarousel) {
|
if (matchedGroup == macroMarkersCarousel) {
|
||||||
return contentIndex == 0 && macroMarkersCarouselGroupList.check(buffer).isFiltered();
|
return contentIndex == 0 && macroMarkersCarouselGroupList.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == horizontalShelf) {
|
|
||||||
return cellVideoAttribute.check(buffer).isFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -36,7 +36,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
* - Some layout component residue will remain, such as the video chapter previews for some search results.
|
* - Some layout component residue will remain, such as the video chapter previews for some search results.
|
||||||
* These components do not include the video title or channel name, and they
|
* These components do not include the video title or channel name, and they
|
||||||
* appear outside the filtered components so they are not caught.
|
* appear outside the filtered components so they are not caught.
|
||||||
* - Keywords are case sensitive, but some casing variation is manually added.
|
* - Keywords are case-sensitive, but some casing variation is manually added.
|
||||||
* (ie: "mr beast" automatically filters "Mr Beast" and "MR BEAST").
|
* (ie: "mr beast" automatically filters "Mr Beast" and "MR BEAST").
|
||||||
* - Keywords present in the layout or video data cannot be used as filters, otherwise all videos
|
* - Keywords present in the layout or video data cannot be used as filters, otherwise all videos
|
||||||
* will always be hidden. This patch checks for some words of these words.
|
* will always be hidden. This patch checks for some words of these words.
|
||||||
@@ -46,7 +46,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
public final class KeywordContentFilter extends Filter {
|
public final class KeywordContentFilter extends Filter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strings found in the buffer for every videos. Full strings should be specified.
|
* Strings found in the buffer for every video. Full strings should be specified.
|
||||||
*
|
*
|
||||||
* This list does not include every common buffer string, and this can be added/changed as needed.
|
* This list does not include every common buffer string, and this can be added/changed as needed.
|
||||||
* Words must be entered with the exact casing as found in the buffer.
|
* Words must be entered with the exact casing as found in the buffer.
|
||||||
@@ -190,7 +190,7 @@ public final class KeywordContentFilter extends Filter {
|
|||||||
return sentence;
|
return sentence;
|
||||||
}
|
}
|
||||||
final int firstCodePoint = sentence.codePointAt(0);
|
final int firstCodePoint = sentence.codePointAt(0);
|
||||||
// In some non English languages title case is different than uppercase.
|
// In some non-English languages title case is different from uppercase.
|
||||||
return new StringBuilder()
|
return new StringBuilder()
|
||||||
.appendCodePoint(Character.toTitleCase(firstCodePoint))
|
.appendCodePoint(Character.toTitleCase(firstCodePoint))
|
||||||
.append(sentence, Character.charCount(firstCodePoint), sentence.length())
|
.append(sentence, Character.charCount(firstCodePoint), sentence.length())
|
||||||
@@ -206,7 +206,7 @@ public final class KeywordContentFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int delimiter = ' ';
|
final int delimiter = ' ';
|
||||||
// Use code points and not characters to handle unicode surrogates.
|
// Use code points and not characters to handle Unicode surrogates.
|
||||||
int[] codePoints = sentence.codePoints().toArray();
|
int[] codePoints = sentence.codePoints().toArray();
|
||||||
boolean capitalizeNext = true;
|
boolean capitalizeNext = true;
|
||||||
for (int i = 0, length = codePoints.length; i < length; i++) {
|
for (int i = 0, length = codePoints.length; i < length; i++) {
|
||||||
@@ -376,7 +376,7 @@ public final class KeywordContentFilter extends Filter {
|
|||||||
return phrase.substring(1, phrase.length() - 1);
|
return phrase.substring(1, phrase.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
|
private synchronized void parseKeywords() { // Must be synchronized since Litho is multithreaded.
|
||||||
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
||||||
|
|
||||||
//noinspection StringEquality
|
//noinspection StringEquality
|
||||||
@@ -417,14 +417,14 @@ public final class KeywordContentFilter extends Filter {
|
|||||||
|
|
||||||
// Common casing that might appear.
|
// Common casing that might appear.
|
||||||
//
|
//
|
||||||
// This could be simplified by adding case insensitive search to the prefix search,
|
// This could be simplified by adding case-insensitive search to the prefix search,
|
||||||
// which is very simple to add to StringTreSearch for Unicode and ByteTrieSearch for ASCII.
|
// which is very simple to add to StringTreSearch for Unicode and ByteTrieSearch for ASCII.
|
||||||
//
|
//
|
||||||
// But to support Unicode with ByteTrieSearch would require major changes because
|
// But to support Unicode with ByteTrieSearch would require major changes because
|
||||||
// UTF-8 characters can be different byte lengths, which does
|
// UTF-8 characters can be different byte lengths, which does
|
||||||
// not allow comparing two different byte arrays using simple plain array indexes.
|
// not allow comparing two different byte arrays using simple plain array indexes.
|
||||||
//
|
//
|
||||||
// Instead use all common case variations of the words.
|
// Instead, use all common case variations of the words.
|
||||||
String[] phraseVariations = {
|
String[] phraseVariations = {
|
||||||
phrase,
|
phrase,
|
||||||
phrase.toLowerCase(),
|
phrase.toLowerCase(),
|
||||||
@@ -556,7 +556,7 @@ public final class KeywordContentFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+368
-42
@@ -7,18 +7,30 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.patches.litho.Filter;
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
import app.revanced.extension.shared.patches.litho.FilterGroupList.StringFilterGroupList;
|
||||||
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
|
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
@@ -27,7 +39,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LayoutComponentsFilter extends Filter {
|
public final class LayoutComponentsFilter extends Filter {
|
||||||
private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch(
|
private static final StringTrieSearch mixPlaylistsContextExceptions = new StringTrieSearch(
|
||||||
"V.ED", // Playlist browse id.
|
"V.ED", // Playlist browseId.
|
||||||
"java.lang.ref.WeakReference"
|
"java.lang.ref.WeakReference"
|
||||||
);
|
);
|
||||||
private static final ByteArrayFilterGroup mixPlaylistsBufferExceptions = new ByteArrayFilterGroup(
|
private static final ByteArrayFilterGroup mixPlaylistsBufferExceptions = new ByteArrayFilterGroup(
|
||||||
@@ -40,24 +52,43 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"&list="
|
"&list="
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final String PAGE_HEADER_PATH = "page_header.e";
|
private static final List<String> channelTabFilterStrings;
|
||||||
|
private static final List<String> flyoutMenuFilterStrings;
|
||||||
|
|
||||||
|
static {
|
||||||
|
channelTabFilterStrings = getFilterStrings(Settings.HIDE_CHANNEL_TAB_FILTER_STRINGS);
|
||||||
|
flyoutMenuFilterStrings = getFilterStrings(Settings.HIDE_FEED_FLYOUT_MENU_FILTER_STRINGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getFilterStrings(StringSetting setting) {
|
||||||
|
String[] filterArray = setting.get().split("\\n");
|
||||||
|
List<String> filters = new ArrayList<>(filterArray.length);
|
||||||
|
|
||||||
|
for (String line : filterArray) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty()) filters.add(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private final StringFilterGroup communityPosts;
|
private final StringFilterGroup communityPosts;
|
||||||
private final StringFilterGroup surveys;
|
private final StringFilterGroup surveys;
|
||||||
private final StringFilterGroup subscribeButton;
|
|
||||||
private final StringFilterGroup notifyMe;
|
private final StringFilterGroup notifyMe;
|
||||||
private final StringFilterGroup singleItemInformationPanel;
|
private final StringFilterGroup singleItemInformationPanel;
|
||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
private final StringFilterGroup compactChannelBarInner;
|
private final StringFilterGroup compactChannelBarInner;
|
||||||
private final StringFilterGroup compactChannelBarInnerButton;
|
private final StringFilterGroup compactChannelBarInnerButton;
|
||||||
private final ByteArrayFilterGroup joinMembershipButton;
|
private final ByteArrayFilterGroup joinMembershipButton;
|
||||||
private final StringFilterGroup horizontalShelves;
|
|
||||||
private final ByteArrayFilterGroup ticketShelfBuffer;
|
|
||||||
private final StringFilterGroup chipBar;
|
private final StringFilterGroup chipBar;
|
||||||
private final StringFilterGroup channelProfile;
|
private final StringFilterGroup channelProfile;
|
||||||
private final ByteArrayFilterGroupList channelProfileBuffer;
|
private final StringFilterGroupList channelProfileGroupList;
|
||||||
|
private final StringFilterGroup horizontalShelves;
|
||||||
private final ByteArrayFilterGroup playablesBuffer;
|
private final ByteArrayFilterGroup playablesBuffer;
|
||||||
|
private final ByteArrayFilterGroup ticketShelfBuffer;
|
||||||
|
private final ByteArrayFilterGroup playerShoppingShelfBuffer;
|
||||||
|
private final ByteTrieSearch descriptionSearch;
|
||||||
|
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@@ -71,11 +102,28 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
// Identifiers.
|
// Identifiers.
|
||||||
|
|
||||||
|
final var cellDivider = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COMPACT_BANNER,
|
||||||
|
// Empty padding and a relic from very old YT versions. Not related to compact banner but included here to avoid adding another setting.
|
||||||
|
"cell_divider"
|
||||||
|
);
|
||||||
|
|
||||||
final var chipsShelf = new StringFilterGroup(
|
final var chipsShelf = new StringFilterGroup(
|
||||||
Settings.HIDE_CHIPS_SHELF,
|
Settings.HIDE_CHIPS_SHELF,
|
||||||
"chips_shelf"
|
"chips_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var liveChatReplay = new StringFilterGroup(
|
||||||
|
Settings.HIDE_LIVE_CHAT_REPLAY_BUTTON,
|
||||||
|
"live_chat_ep_entrypoint.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
addIdentifierCallbacks(
|
||||||
|
cellDivider,
|
||||||
|
chipsShelf,
|
||||||
|
liveChatReplay
|
||||||
|
);
|
||||||
|
|
||||||
final var visualSpacer = new StringFilterGroup(
|
final var visualSpacer = new StringFilterGroup(
|
||||||
Settings.HIDE_VISUAL_SPACER,
|
Settings.HIDE_VISUAL_SPACER,
|
||||||
"cell_divider"
|
"cell_divider"
|
||||||
@@ -83,6 +131,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
addIdentifierCallbacks(
|
addIdentifierCallbacks(
|
||||||
chipsShelf,
|
chipsShelf,
|
||||||
|
liveChatReplay,
|
||||||
visualSpacer
|
visualSpacer
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -126,6 +175,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"subscriptions_chip_bar"
|
"subscriptions_chip_bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var subscribedChannelsBar = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SUBSCRIBED_CHANNELS_BAR,
|
||||||
|
"subscriptions_channel_bar"
|
||||||
|
);
|
||||||
|
|
||||||
chipBar = new StringFilterGroup(
|
chipBar = new StringFilterGroup(
|
||||||
Settings.HIDE_FILTER_BAR_FEED_IN_HISTORY,
|
Settings.HIDE_FILTER_BAR_FEED_IN_HISTORY,
|
||||||
"chip_bar"
|
"chip_bar"
|
||||||
@@ -272,31 +326,41 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"endorsement_header_footer.e"
|
"endorsement_header_footer.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var videoTitle = new StringFilterGroup(
|
||||||
|
Settings.HIDE_VIDEO_TITLE,
|
||||||
|
"player_overlay_video_heading.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var webLinkPanel = new StringFilterGroup(
|
||||||
|
Settings.HIDE_WEB_SEARCH_RESULTS,
|
||||||
|
"web_link_panel",
|
||||||
|
"web_result_panel"
|
||||||
|
);
|
||||||
|
|
||||||
channelProfile = new StringFilterGroup(
|
channelProfile = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"channel_profile.e",
|
"channel_profile.e",
|
||||||
PAGE_HEADER_PATH
|
"page_header.e"
|
||||||
);
|
);
|
||||||
channelProfileBuffer = new ByteArrayFilterGroupList();
|
channelProfileGroupList = new StringFilterGroupList();
|
||||||
channelProfileBuffer.addAll(new ByteArrayFilterGroup(
|
channelProfileGroupList.addAll(new StringFilterGroup(
|
||||||
Settings.HIDE_STORE_BUTTON,
|
|
||||||
"store_button"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_COMMUNITY_BUTTON,
|
Settings.HIDE_COMMUNITY_BUTTON,
|
||||||
"community_button"
|
"community_button"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_JOIN_BUTTON,
|
Settings.HIDE_JOIN_BUTTON,
|
||||||
"sponsor_button"
|
"sponsor_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_STORE_BUTTON,
|
||||||
|
"header_store_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_SUBSCRIBE_BUTTON_IN_CHANNEL_PAGE,
|
||||||
|
"subscribe_button"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
subscribeButton = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SUBSCRIBE_BUTTON_IN_CHANNEL_PAGE,
|
|
||||||
"subscribe_button"
|
|
||||||
);
|
|
||||||
|
|
||||||
horizontalShelves = new StringFilterGroup(
|
horizontalShelves = new StringFilterGroup(
|
||||||
null, // Setting is checked in isFiltered()
|
null, // Setting is checked in isFiltered()
|
||||||
"horizontal_video_shelf.e",
|
"horizontal_video_shelf.e",
|
||||||
@@ -310,6 +374,38 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"ticket_item.e"
|
"ticket_item.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"shopping_item_card_list"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Work around for unique situation where filtering is based on the setting,
|
||||||
|
// but it must not fall over to other filters if the setting is _not_ enabled.
|
||||||
|
// This is only needed for the horizontal shelf that is used so extensively everywhere.
|
||||||
|
descriptionSearch = new ByteTrieSearch();
|
||||||
|
List.of(
|
||||||
|
new Pair<>(Settings.HIDE_FEATURED_PLACES_SECTION, "yt_fill_star"),
|
||||||
|
new Pair<>(Settings.HIDE_FEATURED_PLACES_SECTION, "yt_fill_experimental_star"),
|
||||||
|
new Pair<>(Settings.HIDE_GAMING_SECTION, "yt_outline_gaming"),
|
||||||
|
new Pair<>(Settings.HIDE_GAMING_SECTION, "yt_outline_experimental_gaming"),
|
||||||
|
new Pair<>(Settings.HIDE_MUSIC_SECTION, "yt_outline_audio"),
|
||||||
|
new Pair<>(Settings.HIDE_MUSIC_SECTION, "yt_outline_experimental_audio"),
|
||||||
|
new Pair<>(Settings.HIDE_QUIZZES_SECTION, "post_base_wrapper_slim"),
|
||||||
|
// May no longer work on v20.31+, even though the component is still there.
|
||||||
|
new Pair<>(Settings.HIDE_ATTRIBUTES_SECTION, "cell_video_attribute")
|
||||||
|
).forEach(pair -> {
|
||||||
|
BooleanSetting setting = pair.first;
|
||||||
|
descriptionSearch.addPattern(pair.second.getBytes(StandardCharsets.UTF_8),
|
||||||
|
(textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
|
||||||
|
//noinspection unchecked
|
||||||
|
AtomicReference<Boolean> hide = (AtomicReference<Boolean>) callbackParameter;
|
||||||
|
hide.set(setting.get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
artistCard,
|
artistCard,
|
||||||
audioTrackButton,
|
audioTrackButton,
|
||||||
@@ -337,21 +433,23 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
quickActions,
|
quickActions,
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
singleItemInformationPanel,
|
singleItemInformationPanel,
|
||||||
subscribeButton,
|
subscribedChannelsBar,
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
subscriptionsChipBar,
|
subscriptionsChipBar,
|
||||||
surveys,
|
surveys,
|
||||||
timedReactions,
|
timedReactions,
|
||||||
videoRecommendationLabels
|
videoTitle,
|
||||||
|
videoRecommendationLabels,
|
||||||
|
webLinkPanel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// This identifier is used not only in players but also in search results:
|
// This identifier is used not only in players but also in search results:
|
||||||
// https://github.com/ReVanced/revanced-patches/issues/3245
|
// https://github.com/ReVanced/revanced-patches/issues/3245
|
||||||
// Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results.
|
// Until 2024, medical information panels such as Covid-19 also used this identifier and were shown in the search results.
|
||||||
// From 2025, the medical information panel is no longer shown in the search results.
|
// From 2025, the medical information panel is no longer shown in the search results.
|
||||||
// Therefore, this identifier does not filter when the search bar is activated.
|
// Therefore, this identifier does not filter when the search bar is activated.
|
||||||
if (matchedGroup == singleItemInformationPanel) {
|
if (matchedGroup == singleItemInformationPanel) {
|
||||||
@@ -365,14 +463,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == channelProfile) {
|
if (matchedGroup == channelProfile) {
|
||||||
return channelProfileBuffer.check(buffer).isFiltered();
|
return channelProfileGroupList.check(accessibility).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == subscribeButton) {
|
if (matchedGroup == communityPosts
|
||||||
return path.startsWith(PAGE_HEADER_PATH);
|
&& NavigationBar.isBackButtonVisible()
|
||||||
}
|
&& !NavigationBar.isSearchBarActive()
|
||||||
|
&& PlayerType.getCurrent() != PlayerType.WATCH_WHILE_MAXIMIZED) {
|
||||||
if (matchedGroup == communityPosts && NavigationBar.isBackButtonVisible()) {
|
|
||||||
// Allow community posts on channel profile page,
|
// Allow community posts on channel profile page,
|
||||||
// or if viewing an individual channel in the feed.
|
// or if viewing an individual channel in the feed.
|
||||||
return false;
|
return false;
|
||||||
@@ -387,18 +484,43 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
&& joinMembershipButton.check(buffer).isFiltered();
|
&& joinMembershipButton.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Horizontal shelves are used everywhere in the app. And to prevent the generic "hide shelves"
|
||||||
|
// from incorrectly hiding other stuff that has its own hide filters,
|
||||||
|
// the more specific shelf filters must check first _and_ they must halt falling over
|
||||||
|
// to other filters if the buffer matches but the setting is off.
|
||||||
if (matchedGroup == horizontalShelves) {
|
if (matchedGroup == horizontalShelves) {
|
||||||
if (contentIndex != 0) return false;
|
if (contentIndex != 0) return false;
|
||||||
|
|
||||||
|
AtomicReference<Boolean> descriptionFilterResult = new AtomicReference<>(null);
|
||||||
|
if (descriptionSearch.matches(buffer, descriptionFilterResult)) {
|
||||||
|
return descriptionFilterResult.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if others are off before searching.
|
||||||
final boolean hideShelves = Settings.HIDE_HORIZONTAL_SHELVES.get();
|
final boolean hideShelves = Settings.HIDE_HORIZONTAL_SHELVES.get();
|
||||||
final boolean hideTickets = Settings.HIDE_TICKET_SHELF.get();
|
final boolean hideTickets = Settings.HIDE_TICKET_SHELF.get();
|
||||||
final boolean hidePlayables = Settings.HIDE_PLAYABLES.get();
|
final boolean hidePlayables = Settings.HIDE_PLAYABLES.get();
|
||||||
|
final boolean hidePlayerShoppingShelf = Settings.HIDE_CREATOR_STORE_SHELF.get();
|
||||||
|
if (!hideShelves && !hideTickets && !hidePlayables && !hidePlayerShoppingShelf)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!hideShelves && !hideTickets && !hidePlayables) return false;
|
|
||||||
|
|
||||||
// Must always check other buffers first, to prevent incorrectly hiding them
|
|
||||||
// if they are set to show but hide horizontal shelves is set to hidden.
|
|
||||||
if (ticketShelfBuffer.check(buffer).isFiltered()) return hideTickets;
|
if (ticketShelfBuffer.check(buffer).isFiltered()) return hideTickets;
|
||||||
if (playablesBuffer.check(buffer).isFiltered()) return hidePlayables;
|
if (playablesBuffer.check(buffer).isFiltered()) return hidePlayables;
|
||||||
|
if (playerShoppingShelfBuffer.check(buffer).isFiltered())
|
||||||
|
return hidePlayerShoppingShelf;
|
||||||
|
|
||||||
|
// 20.31+ when exiting fullscreen after watching for a while or when resuming the app,
|
||||||
|
// then sometimes the buffer isn't correct and the player shopping shelf is shown.
|
||||||
|
// If filtering reaches this point then there are no more shelves that could be in the player.
|
||||||
|
// If shopping shelves are set to hidden and the player is active, then assume
|
||||||
|
// it's the shopping shelf.
|
||||||
|
if (hidePlayerShoppingShelf) {
|
||||||
|
PlayerType type = PlayerType.getCurrent();
|
||||||
|
if (type == PlayerType.WATCH_WHILE_MAXIMIZED || type == PlayerType.WATCH_WHILE_FULLSCREEN
|
||||||
|
|| type == PlayerType.WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return hideShelves && hideShelves();
|
return hideShelves && hideShelves();
|
||||||
}
|
}
|
||||||
@@ -474,6 +596,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return original || Settings.HIDE_FLOATING_MICROPHONE_BUTTON.get();
|
return original || Settings.HIDE_FLOATING_MICROPHONE_BUTTON.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideLatestVideosButton(View view) {
|
||||||
|
Utils.hideViewUnderCondition(Settings.HIDE_LATEST_VIDEOS_BUTTON.get(), view);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -523,22 +652,111 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
imageView.setImageDrawable(replacement);
|
imageView.setImageDrawable(replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final FrameLayout.LayoutParams EMPTY_LAYOUT_PARAMS = new FrameLayout.LayoutParams(0, 0);
|
||||||
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ShowMoreButton should not always be hidden.
|
||||||
|
* According to the preference summary, only the ShowMoreButton in search results is hidden.
|
||||||
|
* Since the ShowMoreButton should be visible on other pages, such as channels,
|
||||||
|
* the original values of the Views are saved in fields.
|
||||||
|
*/
|
||||||
|
private static FrameLayout.LayoutParams cachedLayoutParams;
|
||||||
|
private static int cachedButtonContainerMinimumHeight = -1;
|
||||||
|
private static int cachedPlaceHolderMinimumHeight = -1;
|
||||||
|
private static int cachedRootViewMinimumHeight = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void hideShowMoreButton(View view) {
|
public static void hideShowMoreButton(View view, View buttonContainer, TextView textView) {
|
||||||
if (HIDE_SHOW_MORE_BUTTON_ENABLED
|
if (HIDE_SHOW_MORE_BUTTON_ENABLED
|
||||||
&& NavigationBar.isSearchBarActive()
|
&& view instanceof ViewGroup rootView
|
||||||
// Search bar can be active but behind the player.
|
&& buttonContainer != null
|
||||||
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
&& textView != null
|
||||||
// FIXME: "Show more" button is visible hidden,
|
&& buttonContainer.getLayoutParams() instanceof FrameLayout.LayoutParams lp
|
||||||
// but an empty space remains that can be clicked.
|
) {
|
||||||
Utils.hideViewByLayoutParams(view);
|
View placeHolder = rootView.getChildAt(0);
|
||||||
|
|
||||||
|
// For some users, ShowMoreButton has a PlaceHolder ViewGroup (A/B tests).
|
||||||
|
// When a PlaceHolder is present, a different method is used to hide or show the ViewGroup.
|
||||||
|
boolean hasPlaceHolder = placeHolder instanceof FrameLayout;
|
||||||
|
|
||||||
|
// Only in search results, the content description of RootView and the text of TextView match.
|
||||||
|
// Hide ShowMoreButton in search results, but show ShowMoreButton in other pages (e.g. channels).
|
||||||
|
boolean isSearchResults = TextUtils.equals(rootView.getContentDescription(), textView.getText());
|
||||||
|
|
||||||
|
if (hasPlaceHolder) {
|
||||||
|
hideShowMoreButtonWithPlaceHolder(placeHolder, isSearchResults);
|
||||||
|
} else {
|
||||||
|
hideShowMoreButtonWithOutPlaceHolder(buttonContainer, lp, isSearchResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedRootViewMinimumHeight == -1) {
|
||||||
|
cachedRootViewMinimumHeight = rootView.getMinimumHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSearchResults) {
|
||||||
|
rootView.setMinimumHeight(0);
|
||||||
|
rootView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
rootView.setMinimumHeight(cachedRootViewMinimumHeight);
|
||||||
|
rootView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void hideShowMoreButtonWithPlaceHolder(View placeHolder, boolean isSearchResults) {
|
||||||
|
if (cachedPlaceHolderMinimumHeight == -1) {
|
||||||
|
cachedPlaceHolderMinimumHeight = placeHolder.getMinimumHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSearchResults) {
|
||||||
|
placeHolder.setMinimumHeight(0);
|
||||||
|
placeHolder.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
placeHolder.setMinimumHeight(cachedPlaceHolderMinimumHeight);
|
||||||
|
placeHolder.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hideShowMoreButtonWithOutPlaceHolder(View buttonContainer, FrameLayout.LayoutParams lp,
|
||||||
|
boolean isSearchResults) {
|
||||||
|
if (cachedButtonContainerMinimumHeight == -1) {
|
||||||
|
cachedButtonContainerMinimumHeight = buttonContainer.getMinimumHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedLayoutParams == null) {
|
||||||
|
cachedLayoutParams = lp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSearchResults) {
|
||||||
|
buttonContainer.setMinimumHeight(0);
|
||||||
|
buttonContainer.setLayoutParams(EMPTY_LAYOUT_PARAMS);
|
||||||
|
buttonContainer.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
buttonContainer.setMinimumHeight(cachedButtonContainerMinimumHeight);
|
||||||
|
buttonContainer.setLayoutParams(cachedLayoutParams);
|
||||||
|
buttonContainer.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideSubscribedChannelsBar(View view) {
|
||||||
|
Utils.hideViewByRemovingFromParentUnderCondition(Settings.HIDE_SUBSCRIBED_CHANNELS_BAR, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int hideSubscribedChannelsBar(int original) {
|
||||||
|
return Settings.HIDE_SUBSCRIBED_CHANNELS_BAR.get()
|
||||||
|
? 0
|
||||||
|
: original;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean hideShelves() {
|
private static boolean hideShelves() {
|
||||||
// Horizontal shelves are used for music/game links in video descriptions,
|
// Horizontal shelves are used for music/game links in video descriptions,
|
||||||
// such as https://youtube.com/watch?v=W8kI1na3S2M
|
// such as https://youtube.com/watch?v=W8kI1na3S2M
|
||||||
@@ -621,4 +839,112 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* Hide feed flyout menu for phone
|
||||||
|
*
|
||||||
|
* @param menuTitleCharSequence menu title
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static CharSequence hideFlyoutMenu(@Nullable CharSequence menuTitleCharSequence) {
|
||||||
|
if (menuTitleCharSequence == null || !Settings.HIDE_FEED_FLYOUT_MENU.get()
|
||||||
|
|| flyoutMenuFilterStrings.isEmpty()) {
|
||||||
|
return menuTitleCharSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
String menuTitleString = menuTitleCharSequence.toString();
|
||||||
|
|
||||||
|
for (String filter : flyoutMenuFilterStrings) {
|
||||||
|
if (menuTitleString.equalsIgnoreCase(filter)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuTitleCharSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* hide feed flyout panel for tablet
|
||||||
|
*
|
||||||
|
* @param menuTextView flyout text view
|
||||||
|
* @param menuTitleCharSequence raw text
|
||||||
|
*/
|
||||||
|
public static void hideFlyoutMenu(TextView menuTextView, CharSequence menuTitleCharSequence) {
|
||||||
|
if (menuTitleCharSequence == null || !Settings.HIDE_FEED_FLYOUT_MENU.get()
|
||||||
|
|| flyoutMenuFilterStrings.isEmpty()
|
||||||
|
|| !(menuTextView.getParent() instanceof View parentView)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String menuTitleString = menuTitleCharSequence.toString();
|
||||||
|
|
||||||
|
for (String filter : flyoutMenuFilterStrings) {
|
||||||
|
if (menuTitleString.equalsIgnoreCase(filter)) {
|
||||||
|
Utils.hideViewByLayoutParams(parentView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* Rather than simply hiding the channel tab view, completely remove the channel tab from the list.
|
||||||
|
* If a channel tab is removed from the list, users will not be able to open it by swiping.
|
||||||
|
*
|
||||||
|
* @param channelTabText Text assigned to the channel tab, such as "Shorts", "Playlists",
|
||||||
|
* "Community", "Store". This text follows the user's language.
|
||||||
|
* @return Whether to remove the channel tab from the list.
|
||||||
|
*/
|
||||||
|
public static boolean hideChannelTab(@Nullable String channelTabText) {
|
||||||
|
if (channelTabText == null || !Settings.HIDE_CHANNEL_TAB.get()
|
||||||
|
|| channelTabFilterStrings.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String filter : channelTabFilterStrings) {
|
||||||
|
if (channelTabText.equalsIgnoreCase(filter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @param typedString Keywords typed in the search bar.
|
||||||
|
* @return Whether the setting is enabled and the typed string is empty.
|
||||||
|
*/
|
||||||
|
public static boolean hideYouMayLikeSection(String typedString) {
|
||||||
|
return Settings.HIDE_YOU_MAY_LIKE_SECTION.get()
|
||||||
|
// The 'You may like' section is only visible when no search terms are entered.
|
||||||
|
// To avoid unnecessary collection traversals, filtering is performed only when the typedString is empty.
|
||||||
|
&& TextUtils.isEmpty(typedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @param searchTerm This class contains information related to search terms.
|
||||||
|
* The {@code toString()} method of this class overrides the search term.
|
||||||
|
* @param endpoint Endpoint related with the search term.
|
||||||
|
* For search history, this value is:
|
||||||
|
* '/complete/deleteitems?client=youtube-android-pb&delq=${searchTerm}&deltok=${token}'.
|
||||||
|
* For search suggestions, this value is null or empty.
|
||||||
|
* @return Whether search term is a search history or not.
|
||||||
|
*/
|
||||||
|
public static boolean isSearchHistory(Object searchTerm, String endpoint) {
|
||||||
|
boolean isSearchHistory = endpoint != null && endpoint.contains("/delete");
|
||||||
|
if (!isSearchHistory) {
|
||||||
|
Logger.printDebug(() -> "Remove search suggestion: " + searchTerm);
|
||||||
|
}
|
||||||
|
return isSearchHistory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ public final class PlaybackSpeedMenuFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == oldPlaybackMenuGroup) {
|
if (matchedGroup == oldPlaybackMenuGroup) {
|
||||||
isOldPlaybackSpeedMenuVisible = true;
|
isOldPlaybackSpeedMenuVisible = true;
|
||||||
|
|||||||
+40
-26
@@ -7,6 +7,7 @@ import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilt
|
|||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
|
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
|
|
||||||
@@ -44,61 +45,74 @@ public final class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
flyoutFilterGroupList.addAll(
|
flyoutFilterGroupList.addAll(
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_CAPTIONS,
|
Settings.HIDE_PLAYER_FLYOUT_CAPTIONS,
|
||||||
"closed_caption_"
|
"closed_caption_",
|
||||||
),
|
"yt_outline_experimental_closed_captions_"
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS,
|
|
||||||
"yt_outline_gear_"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_PLAYER_FLYOUT_LOOP_VIDEO,
|
|
||||||
"yt_outline_arrow_repeat_1_"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_PLAYER_FLYOUT_AMBIENT_MODE,
|
|
||||||
"yt_outline_screen_light_"
|
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_PLAYER_FLYOUT_STABLE_VOLUME,
|
|
||||||
"volume_stable_"
|
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_LISTEN_WITH_YOUTUBE_MUSIC,
|
Settings.HIDE_PLAYER_FLYOUT_LISTEN_WITH_YOUTUBE_MUSIC,
|
||||||
"yt_outline_youtube_music_"
|
"yt_outline_youtube_music_",
|
||||||
|
"yt_outline_experimental_youtube_music_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_HELP,
|
Settings.HIDE_PLAYER_FLYOUT_HELP,
|
||||||
"yt_outline_question_circle_"
|
"yt_outline_question_circle_",
|
||||||
|
"yt_outline_experimental_help_circle_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_LOCK_SCREEN,
|
Settings.HIDE_PLAYER_FLYOUT_LOCK_SCREEN,
|
||||||
"yt_outline_lock_"
|
"yt_outline_lock_",
|
||||||
|
"yt_outline_experimental_lock_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_SPEED,
|
Settings.HIDE_PLAYER_FLYOUT_SPEED,
|
||||||
"yt_outline_play_arrow_half_circle_"
|
"yt_outline_play_arrow_half_circle_",
|
||||||
|
"yt_outline_experimental_play_circle_half_dashed_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_AUDIO_TRACK,
|
Settings.HIDE_PLAYER_FLYOUT_AUDIO_TRACK,
|
||||||
"yt_outline_person_radar_"
|
"yt_outline_person_radar_",
|
||||||
|
"yt_outline_experimental_person_radar_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS,
|
||||||
|
"yt_outline_gear_",
|
||||||
|
"yt_outline_experimental_gear_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_PLAYER_FLYOUT_AMBIENT_MODE,
|
||||||
|
"yt_outline_screen_light_",
|
||||||
|
"yt_outline_experimental_ambient_mode_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_PLAYER_FLYOUT_LOOP_VIDEO,
|
||||||
|
"yt_outline_arrow_repeat_1_",
|
||||||
|
"yt_outline_experimental_repeat1_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_PLAYER_FLYOUT_STABLE_VOLUME,
|
||||||
|
"volume_stable_",
|
||||||
|
"yt_outline_experimental_stable_volume_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_SLEEP_TIMER,
|
Settings.HIDE_PLAYER_FLYOUT_SLEEP_TIMER,
|
||||||
"yt_outline_moon_z_"
|
"yt_outline_moon_z_",
|
||||||
|
"yt_outline_experimental_sleep_timer_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_WATCH_IN_VR,
|
Settings.HIDE_PLAYER_FLYOUT_WATCH_IN_VR,
|
||||||
"yt_outline_vr_"
|
"yt_outline_vr_",
|
||||||
|
"yt_outline_experimental_vr_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_FLYOUT_VIDEO_QUALITY,
|
Settings.HIDE_PLAYER_FLYOUT_VIDEO_QUALITY,
|
||||||
"yt_outline_adjust_"
|
"yt_outline_adjust_",
|
||||||
|
"yt_outline_experimental_adjust_"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == videoQualityMenuFooter) {
|
if (matchedGroup == videoQualityMenuFooter) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+25
-13
@@ -18,21 +18,21 @@ import app.revanced.extension.youtube.patches.VideoInformation;
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for video id's in the proto buffer of Shorts dislike.
|
* Searches for video IDs in the proto buffer of Shorts dislike.
|
||||||
*
|
*
|
||||||
* Because multiple litho dislike spans are created in the background
|
* Because multiple litho dislike spans are created in the background
|
||||||
* (and also anytime litho refreshes the components, which is somewhat arbitrary),
|
* (and also anytime litho refreshes the components, which is somewhat arbitrary),
|
||||||
* that makes the value of {@link VideoInformation#getVideoId()} and {@link VideoInformation#getPlayerResponseVideoId()}
|
* that makes the value of {@link VideoInformation#getVideoId()} and {@link VideoInformation#getPlayerResponseVideoId()}
|
||||||
* unreliable to determine which video id a Shorts litho span belongs to.
|
* unreliable to determine which video ID a Shorts litho span belongs to.
|
||||||
*
|
*
|
||||||
* But the correct video id does appear in the protobuffer just before a Shorts litho span is created.
|
* But the correct video ID does appear in the protobuffer just before a Shorts litho span is created.
|
||||||
*
|
*
|
||||||
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
||||||
*/
|
*/
|
||||||
public final class ReturnYouTubeDislikeFilter extends Filter {
|
public final class ReturnYouTubeDislikeFilter extends Filter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
* Last unique video IDs loaded. Value is ignored and Map is treated as a Set.
|
||||||
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
|
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
|
||||||
*/
|
*/
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@@ -49,7 +49,7 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
|
|||||||
}
|
}
|
||||||
synchronized (lastVideoIds) {
|
synchronized (lastVideoIds) {
|
||||||
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
|
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
|
||||||
Logger.printDebug(() -> "New Short video id: " + videoId);
|
Logger.printDebug(() -> "New Short video ID: " + videoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -64,19 +64,31 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
|
|||||||
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
|
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
|
||||||
// So must check for both buttons.
|
// So must check for both buttons.
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
new StringFilterGroup(null, "|shorts_like_button.e"),
|
new StringFilterGroup(
|
||||||
new StringFilterGroup(null, "|shorts_dislike_button.e")
|
null,
|
||||||
|
"shorts_like_button.e",
|
||||||
|
"reel_like_button.e",
|
||||||
|
"reel_like_toggled_button.e",
|
||||||
|
"shorts_dislike_button.e",
|
||||||
|
"reel_dislike_button.e",
|
||||||
|
"reel_dislike_toggled_button.e"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// After the button identifiers is binary data and then the video id for that specific short.
|
// After the button identifiers is binary data and then the video ID for that specific short.
|
||||||
videoIdFilterGroup.addAll(
|
videoIdFilterGroup.addAll(
|
||||||
new ByteArrayFilterGroup(null, "id.reel_like_button"),
|
new ByteArrayFilterGroup(
|
||||||
new ByteArrayFilterGroup(null, "id.reel_dislike_button")
|
null,
|
||||||
|
"id.reel_like_button",
|
||||||
|
"id.reel_dislike_button",
|
||||||
|
"ic_right_like",
|
||||||
|
"ic_right_dislike"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -86,8 +98,8 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
|
|||||||
if (result.isFiltered()) {
|
if (result.isFiltered()) {
|
||||||
String matchedVideoId = findVideoId(buffer);
|
String matchedVideoId = findVideoId(buffer);
|
||||||
// Matched video will be null if in incognito mode.
|
// Matched video will be null if in incognito mode.
|
||||||
// Must pass a null id to correctly clear out the current video data.
|
// Must pass a null ID to correctly clear out the current video data.
|
||||||
// Otherwise if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,
|
// Otherwise, if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,
|
||||||
// the new incognito Short will show the old prior data.
|
// the new incognito Short will show the old prior data.
|
||||||
ReturnYouTubeDislikePatch.setLastLithoShortsVideoId(matchedVideoId);
|
ReturnYouTubeDislikePatch.setLastLithoShortsVideoId(matchedVideoId);
|
||||||
}
|
}
|
||||||
|
|||||||
+140
-33
@@ -4,10 +4,12 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.patches.litho.Filter;
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
||||||
|
import app.revanced.extension.shared.patches.litho.LithoFilterPatch;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||||
|
|
||||||
@@ -20,10 +22,11 @@ import java.util.Map;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.EngagementPanel;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||||
public final class ShortsFilter extends Filter {
|
public final class ShortsFilter extends Filter {
|
||||||
private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get();
|
private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get();
|
||||||
private static final String COMPONENT_TYPE = "ComponentType";
|
private static final String COMPONENT_TYPE = "ComponentType";
|
||||||
@@ -67,18 +70,22 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
private final StringFilterGroup shortsCompactFeedVideo;
|
private final StringFilterGroup shortsCompactFeedVideo;
|
||||||
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
||||||
|
private final StringFilterGroup channelProfile;
|
||||||
|
private final ByteArrayFilterGroup channelProfileShelfHeaderBuffer;
|
||||||
private final StringFilterGroup useSoundButton;
|
private final StringFilterGroup useSoundButton;
|
||||||
private final ByteArrayFilterGroup useSoundButtonBuffer;
|
private final ByteArrayFilterGroup useSoundButtonBuffer;
|
||||||
private final StringFilterGroup useTemplateButton;
|
private final StringFilterGroup useTemplateButton;
|
||||||
private final ByteArrayFilterGroup useTemplateButtonBuffer;
|
private final ByteArrayFilterGroup useTemplateButtonBuffer;
|
||||||
private final StringFilterGroup reelCarousel;
|
|
||||||
private final ByteArrayFilterGroup reelCarouselBuffer;
|
|
||||||
|
|
||||||
private final StringFilterGroup autoDubbedLabel;
|
private final StringFilterGroup autoDubbedLabel;
|
||||||
private final StringFilterGroup subscribeButton;
|
private final StringFilterGroup subscribeButton;
|
||||||
private final StringFilterGroup joinButton;
|
private final StringFilterGroup joinButton;
|
||||||
private final StringFilterGroup paidPromotionLabel;
|
private final StringFilterGroup paidPromotionLabel;
|
||||||
private final StringFilterGroup shelfHeader;
|
private final StringFilterGroup shelfHeaderIdentifier;
|
||||||
|
private final StringFilterGroup shelfHeaderPath;
|
||||||
|
|
||||||
|
private final StringFilterGroup reelCarousel;
|
||||||
|
private final ByteArrayFilterGroupList reelCarouselBuffer = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
private final StringFilterGroup suggestedAction;
|
private final StringFilterGroup suggestedAction;
|
||||||
private final ByteArrayFilterGroupList suggestedActionsBuffer = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList suggestedActionsBuffer = new ByteArrayFilterGroupList();
|
||||||
@@ -97,24 +104,34 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_shelf",
|
"shorts_shelf",
|
||||||
"inline_shorts",
|
"inline_shorts",
|
||||||
"shorts_grid",
|
"shorts_grid",
|
||||||
"shorts_video_cell",
|
"shorts_video_cell"
|
||||||
|
);
|
||||||
|
|
||||||
|
channelProfile = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_CHANNEL,
|
||||||
"shorts_pivot_item"
|
"shorts_pivot_item"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
channelProfileShelfHeaderBuffer = new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_CHANNEL,
|
||||||
|
"Shorts"
|
||||||
|
);
|
||||||
|
|
||||||
// Feed Shorts shelf header.
|
// Feed Shorts shelf header.
|
||||||
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
||||||
shelfHeader = new StringFilterGroup(
|
shelfHeaderIdentifier = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"shelf_header.e"
|
"shelf_header.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
addIdentifierCallbacks(shortsIdentifiers, shelfHeader);
|
addIdentifierCallbacks(shortsIdentifiers, channelProfile, shelfHeaderIdentifier);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Path components.
|
// Path components.
|
||||||
//
|
//
|
||||||
|
|
||||||
shortsCompactFeedVideo = new StringFilterGroup(null,
|
shortsCompactFeedVideo = new StringFilterGroup(
|
||||||
|
null,
|
||||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||||
"compact_video.e",
|
"compact_video.e",
|
||||||
// 'video_lockup_with_attachment.e' is shown instead of 'compact_video.e' for some users
|
// 'video_lockup_with_attachment.e' is shown instead of 'compact_video.e' for some users
|
||||||
@@ -125,7 +142,14 @@ public final class ShortsFilter extends Filter {
|
|||||||
// Filter out items that use the 'frame0' thumbnail.
|
// Filter out items that use the 'frame0' thumbnail.
|
||||||
// This is a valid thumbnail for both regular videos and Shorts,
|
// This is a valid thumbnail for both regular videos and Shorts,
|
||||||
// but it appears these thumbnails are used only for Shorts.
|
// but it appears these thumbnails are used only for Shorts.
|
||||||
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"/frame0.jpg");
|
||||||
|
|
||||||
|
shelfHeaderPath = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"shelf_header.e"
|
||||||
|
);
|
||||||
|
|
||||||
// Shorts player components.
|
// Shorts player components.
|
||||||
StringFilterGroup pausedOverlayButtons = new StringFilterGroup(
|
StringFilterGroup pausedOverlayButtons = new StringFilterGroup(
|
||||||
@@ -229,13 +253,21 @@ public final class ShortsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
reelCarousel = new StringFilterGroup(
|
reelCarousel = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
null,
|
||||||
"reel_carousel.e"
|
"reel_carousel.e"
|
||||||
);
|
);
|
||||||
|
|
||||||
reelCarouselBuffer = new ByteArrayFilterGroup(
|
reelCarouselBuffer.addAll(
|
||||||
null,
|
new ByteArrayFilterGroup(
|
||||||
"FEsfv_audio_pivot"
|
Settings.HIDE_SHORTS_AI_BUTTON,
|
||||||
|
"yt_outline_info_circle",
|
||||||
|
"yt_outline_experimental_info_circle"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
||||||
|
"yt_outline_audio", // Doesn't seem to be needed as v20.14.43 uses 'yt_outline_experimental_audio' as well. But still just in case.
|
||||||
|
"yt_outline_experimental_audio"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
useSoundButton = new StringFilterGroup(
|
useSoundButton = new StringFilterGroup(
|
||||||
@@ -279,10 +311,10 @@ public final class ShortsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
shortsCompactFeedVideo, joinButton, subscribeButton, paidPromotionLabel, livePreview,
|
shortsCompactFeedVideo, shelfHeaderPath, joinButton, subscribeButton, paidPromotionLabel,
|
||||||
suggestedAction, pausedOverlayButtons, channelBar, previewComment, autoDubbedLabel,
|
livePreview, suggestedAction, pausedOverlayButtons, channelBar, infoPanel, previewComment,
|
||||||
fullVideoLinkLabel, videoTitle, useSoundButton, reelSoundMetadata, soundButton, reelCarousel,
|
autoDubbedLabel, fullVideoLinkLabel, videoTitle, useSoundButton, soundButton, stickers,
|
||||||
infoPanel, stickers, likeFountain, likeButton, dislikeButton
|
reelCarousel, reelSoundMetadata, likeFountain, likeButton, dislikeButton
|
||||||
);
|
);
|
||||||
|
|
||||||
// Legacy hiding of Shorts action buttons. Because of 20.31+ buffer changes
|
// Legacy hiding of Shorts action buttons. Because of 20.31+ buffer changes
|
||||||
@@ -331,7 +363,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_TAGGED_PRODUCTS,
|
Settings.HIDE_SHORTS_TAGGED_PRODUCTS,
|
||||||
// Product buttons show pictures of the products, and does not have any unique icons to identify.
|
// Product buttons show pictures of the products, and does not have any unique icons to identify.
|
||||||
// Instead use a unique identifier found in the buffer.
|
// Instead, use a unique identifier found in the buffer.
|
||||||
"PAproduct_listZ"
|
"PAproduct_listZ"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
@@ -402,8 +434,23 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (contentType == FilterContentType.IDENTIFIER) {
|
||||||
|
if (matchedGroup == shelfHeaderIdentifier) {
|
||||||
|
// Shelf header reused in history/channel/etc.
|
||||||
|
// Shorts header is always index 0
|
||||||
|
if (contentIndex != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchedGroup == channelProfile) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldHideShortsFeedItems();
|
||||||
|
}
|
||||||
|
|
||||||
if (contentType == FilterContentType.PATH) {
|
if (contentType == FilterContentType.PATH) {
|
||||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton
|
if (matchedGroup == subscribeButton || matchedGroup == joinButton
|
||||||
|| matchedGroup == paidPromotionLabel || matchedGroup == autoDubbedLabel) {
|
|| matchedGroup == paidPromotionLabel || matchedGroup == autoDubbedLabel) {
|
||||||
@@ -428,6 +475,19 @@ public final class ShortsFilter extends Filter {
|
|||||||
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered();
|
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == shelfHeaderPath) {
|
||||||
|
// Shelf header reused in history/channel/etc.
|
||||||
|
// Shorts header is always index 0
|
||||||
|
if (contentIndex != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!channelProfileShelfHeaderBuffer.check(buffer).isFiltered()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldHideShortsFeedItems();
|
||||||
|
}
|
||||||
|
|
||||||
// Video action buttons (comment, share, remix) have the same path.
|
// Video action buttons (comment, share, remix) have the same path.
|
||||||
// Like and dislike are separate path filters and don't require buffer searching.
|
// Like and dislike are separate path filters and don't require buffer searching.
|
||||||
if (matchedGroup == shortsActionBar) {
|
if (matchedGroup == shortsActionBar) {
|
||||||
@@ -449,40 +509,35 @@ public final class ShortsFilter extends Filter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed/search identifier components.
|
return false;
|
||||||
if (matchedGroup == shelfHeader) {
|
|
||||||
// Because the header is used in watch history and possibly other places, check for the index,
|
|
||||||
// which is 0 when the shelf header is used for Shorts.
|
|
||||||
if (contentIndex != 0) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shouldHideShortsFeedItems();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldHideShortsFeedItems() {
|
private boolean shouldHideShortsFeedItems() {
|
||||||
// Known issue if hide home is on but at least one other hide is off:
|
// Known issue if hide home is on but at least one other hide is off:
|
||||||
//
|
//
|
||||||
// Shorts suggestions will load in the background if a video is opened and
|
// Shorts suggestions will load in the background if a video is opened and
|
||||||
// immediately minimized before any suggestions are loaded.
|
// immediately minimized before any suggestions are loaded.
|
||||||
// In this state the player type will show minimized, which cannot
|
// In this state the player type will show minimized, which cannot
|
||||||
// distinguish between Shorts suggestions loading in the player and between
|
// distinguish between Shorts suggestions loading in the player and between
|
||||||
// scrolling thru search/home/subscription tabs while a player is minimized.
|
// scrolling through search/home/subscription tabs while a player is minimized.
|
||||||
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
||||||
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||||
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
||||||
|
final boolean hideVideoDescription = Settings.HIDE_SHORTS_VIDEO_DESCRIPTION.get();
|
||||||
final boolean hideHistory = Settings.HIDE_SHORTS_HISTORY.get();
|
final boolean hideHistory = Settings.HIDE_SHORTS_HISTORY.get();
|
||||||
|
|
||||||
if (!hideHome && !hideSubscriptions && !hideSearch && !hideHistory) {
|
if (!hideHome && !hideSubscriptions && !hideSearch && !hideVideoDescription && !hideHistory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (hideHome && hideSubscriptions && hideSearch && hideHistory) {
|
if (hideHome && hideSubscriptions && hideSearch && hideVideoDescription && hideHistory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must check player type first, as search bar can be active behind the player.
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
// For now, consider the under video results the same as the home feed.
|
return EngagementPanel.isDescription()
|
||||||
return hideHome;
|
? hideVideoDescription // Player video description panel opened.
|
||||||
|
: hideHome; // For now, consider Shorts under video player the same as the home feed.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must check second, as search can be from any tab.
|
// Must check second, as search can be from any tab.
|
||||||
@@ -509,6 +564,58 @@ public final class ShortsFilter extends Filter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* Hide action buttons by index.
|
||||||
|
* <p>
|
||||||
|
* Regular video action buttons vary in order by video, country, and account.
|
||||||
|
* Therefore, hiding buttons by index may hide unintended buttons.
|
||||||
|
* <p>
|
||||||
|
* Shorts action buttons are almost always in the same order.
|
||||||
|
* (From top to bottom: Like, Dislike, Comment, Share, Remix).
|
||||||
|
* Therefore, we can hide Shorts action buttons by index.
|
||||||
|
*
|
||||||
|
* @param pathBuilder Same as pathBuilder used in {@link LithoFilterPatch}.
|
||||||
|
* @param treeNodeResultList List containing Litho components.
|
||||||
|
*/
|
||||||
|
public static void hideActionButtons(StringBuilder pathBuilder, List<Object> treeNodeResultList) {
|
||||||
|
try {
|
||||||
|
if (pathBuilder == null || pathBuilder.length() == 0 || treeNodeResultList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int size = treeNodeResultList.size();
|
||||||
|
|
||||||
|
// The minimum size of the target List is 4.
|
||||||
|
if (size < 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String path = pathBuilder.toString();
|
||||||
|
|
||||||
|
if (!Utils.containsAny(path, REEL_ACTION_BAR_PATHS)
|
||||||
|
// Regular Shorts: [ComponentType, ComponentType, ComponentType, ComponentType, ComponentType]
|
||||||
|
// Shorts ads: [ComponentType, ComponentType, ComponentType, ComponentType] (No Remix button)
|
||||||
|
|| !COMPONENT_TYPE.equals(treeNodeResultList.get(0).toString())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Removing elements without iterating through the list in reverse order will throw an exception.
|
||||||
|
for (int i = size - 1; i > -1; i--) {
|
||||||
|
// treeNodeResult is each button.
|
||||||
|
Object treeNodeResult = treeNodeResultList.get(i);
|
||||||
|
if (treeNodeResult != null) {
|
||||||
|
BooleanSetting setting = REEL_ACTION_BUTTONS_MAP.get(i);
|
||||||
|
if (setting != null && setting.get()) {
|
||||||
|
int finalI = i;
|
||||||
|
Logger.printDebug(() -> "Hiding action button by index: " + finalI + ", key: " + setting.key);
|
||||||
|
treeNodeResultList.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "hideActionButtons failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.litho;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroupList.StringFilterGroupList;
|
||||||
|
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class VideoActionButtonsFilter extends Filter {
|
||||||
|
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e";
|
||||||
|
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e";
|
||||||
|
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e";
|
||||||
|
/**
|
||||||
|
* Video bar path when the video information is collapsed. Seems to shown only with 20.14+
|
||||||
|
*/
|
||||||
|
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.e";
|
||||||
|
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
||||||
|
|
||||||
|
private final StringFilterGroup likeSubscribeGlow;
|
||||||
|
private final StringFilterGroup actionBarGroup;
|
||||||
|
private final StringFilterGroup buttonFilterPathGroup;
|
||||||
|
private final StringFilterGroupList accessibilityButtonsGroupList = new StringFilterGroupList();
|
||||||
|
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
|
public VideoActionButtonsFilter() {
|
||||||
|
actionBarGroup = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
VIDEO_ACTION_BAR_PATH
|
||||||
|
);
|
||||||
|
addIdentifierCallbacks(actionBarGroup);
|
||||||
|
|
||||||
|
|
||||||
|
likeSubscribeGlow = new StringFilterGroup(
|
||||||
|
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
|
||||||
|
"animated_button_border.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
buttonFilterPathGroup = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"|ContainerType|button.e"
|
||||||
|
);
|
||||||
|
|
||||||
|
addPathCallbacks(
|
||||||
|
likeSubscribeGlow,
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
|
"|segmented_like_dislike_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_DOWNLOAD_BUTTON,
|
||||||
|
"|download_button.e"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_SAVE_BUTTON,
|
||||||
|
"|save_to_playlist_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_CLIP_BUTTON,
|
||||||
|
"|clip_button.e"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
addPathCallbacks(buttonFilterPathGroup);
|
||||||
|
|
||||||
|
if (VersionCheckPatch.IS_20_22_OR_GREATER) {
|
||||||
|
// FIXME: Most buttons do not have an accessibilityId.
|
||||||
|
// Instead, they have an accessibilityText, so hiding functionality must be implemented using this
|
||||||
|
// (e.g. custom filter - 'video_action_bar#Hype')
|
||||||
|
accessibilityButtonsGroupList.addAll(
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHARE_BUTTON,
|
||||||
|
"id.video.share.button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
Settings.HIDE_REMIX_BUTTON,
|
||||||
|
"id.video.remix.button"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bufferButtonsGroupList.addAll(
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_REPORT_BUTTON,
|
||||||
|
"yt_outline_flag"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHARE_BUTTON,
|
||||||
|
"yt_outline_share"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_REMIX_BUTTON,
|
||||||
|
"yt_outline_youtube_shorts_plus"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_THANKS_BUTTON,
|
||||||
|
"yt_outline_dollar_sign_heart"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_ASK_BUTTON,
|
||||||
|
"yt_fill_spark"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHOP_BUTTON,
|
||||||
|
"yt_outline_bag"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_STOP_ADS_BUTTON,
|
||||||
|
"yt_outline_slash_circle_left"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_COMMENTS_BUTTON,
|
||||||
|
"yt_outline_message_bubble_right"
|
||||||
|
),
|
||||||
|
// Check for clip button both here and using a path filter,
|
||||||
|
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_CLIP_BUTTON,
|
||||||
|
"yt_outline_scissors"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_HYPE_BUTTON,
|
||||||
|
"yt_outline_star_shooting"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_PROMOTE_BUTTON,
|
||||||
|
"yt_outline_megaphone"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEveryFilterGroupEnabled() {
|
||||||
|
for (var group : pathCallbacks) {
|
||||||
|
if (!group.isEnabled()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonList = VersionCheckPatch.IS_20_22_OR_GREATER
|
||||||
|
? accessibilityButtonsGroupList
|
||||||
|
: bufferButtonsGroupList;
|
||||||
|
for (var group : buttonList) {
|
||||||
|
if (!group.isEnabled()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hideButtons(String path, String accessibility, byte[] buffer) {
|
||||||
|
// Make sure the current path is the right one to avoid false positives.
|
||||||
|
if (!path.startsWith(VIDEO_ACTION_BAR_PATH) && !path.startsWith(COMPACTIFY_VIDEO_ACTION_BAR_PATH)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VersionCheckPatch.IS_20_22_OR_GREATER
|
||||||
|
? accessibilityButtonsGroupList.check(accessibility).isFiltered()
|
||||||
|
: bufferButtonsGroupList.check(buffer).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||||
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (matchedGroup == likeSubscribeGlow) {
|
||||||
|
return path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX)
|
||||||
|
|| path.startsWith(COMPACTIFY_VIDEO_ACTION_BAR_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current matched group is the action bar group,
|
||||||
|
// in case every filter group is enabled, hide the action bar.
|
||||||
|
if (matchedGroup == actionBarGroup) {
|
||||||
|
return isEveryFilterGroupEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == buttonFilterPathGroup) {
|
||||||
|
return hideButtons(path, accessibility, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.playback.quality;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.patches.VideoInformation.isPremiumVideoQuality;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.youtube.patches.VideoInformation.VideoQualityInterface;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class HidePremiumVideoQualityPatch {
|
||||||
|
private static final boolean HIDE_PREMIUM_VIDEO_QUALITY = Settings.HIDE_PREMIUM_VIDEO_QUALITY.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static Object[] hidePremiumVideoQuality(VideoQualityInterface[] qualities) {
|
||||||
|
if (HIDE_PREMIUM_VIDEO_QUALITY && qualities != null && qualities.length > 0) {
|
||||||
|
try {
|
||||||
|
return Arrays.stream(qualities)
|
||||||
|
.filter(quality -> quality != null && !isPremiumVideoQuality(quality))
|
||||||
|
.toArray(VideoQualityInterface[]::new);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to hide Premium video quality", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return qualities;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-4
@@ -3,13 +3,12 @@ package app.revanced.extension.youtube.patches.playback.quality;
|
|||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.NetworkType;
|
import static app.revanced.extension.shared.Utils.NetworkType;
|
||||||
|
|
||||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.IntegerSetting;
|
import app.revanced.extension.shared.settings.IntegerSetting;
|
||||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||||
|
import app.revanced.extension.youtube.patches.VideoInformation.*;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
|
|
||||||
@@ -74,12 +73,12 @@ public class RememberVideoQualityPatch {
|
|||||||
public static void userChangedShortsQuality(int userSelectedQualityIndex) {
|
public static void userChangedShortsQuality(int userSelectedQualityIndex) {
|
||||||
try {
|
try {
|
||||||
if (shouldRememberVideoQuality()) {
|
if (shouldRememberVideoQuality()) {
|
||||||
VideoQuality[] currentQualities = VideoInformation.getCurrentQualities();
|
VideoQualityInterface[] currentQualities = VideoInformation.getCurrentQualities();
|
||||||
if (currentQualities == null) {
|
if (currentQualities == null) {
|
||||||
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
|
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
VideoQuality quality = currentQualities[userSelectedQualityIndex];
|
VideoQualityInterface quality = currentQualities[userSelectedQualityIndex];
|
||||||
saveDefaultQuality(quality.patch_getResolution());
|
saveDefaultQuality(quality.patch_getResolution());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|||||||
+22
-3
@@ -9,7 +9,8 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ThemePatch extends BaseThemePatch {
|
public class ThemePatch extends BaseThemePatch {
|
||||||
public enum SplashScreenAnimationStyle {
|
public enum SplashScreenAnimationStyle {
|
||||||
DEFAULT(0),
|
// 0 int style exists in target app as a fall through default, but its value is repurposed to be disabled.
|
||||||
|
DISABLED(0),
|
||||||
FPS_60_ONE_SECOND(1),
|
FPS_60_ONE_SECOND(1),
|
||||||
FPS_60_TWO_SECOND(2),
|
FPS_60_TWO_SECOND(2),
|
||||||
FPS_60_FIVE_SECOND(3),
|
FPS_60_FIVE_SECOND(3),
|
||||||
@@ -18,7 +19,7 @@ public class ThemePatch extends BaseThemePatch {
|
|||||||
FPS_30_TWO_SECOND(6),
|
FPS_30_TWO_SECOND(6),
|
||||||
FPS_30_FIVE_SECOND(7),
|
FPS_30_FIVE_SECOND(7),
|
||||||
FPS_30_BLACK_AND_WHITE(8);
|
FPS_30_BLACK_AND_WHITE(8);
|
||||||
// There exists a 10th json style used as the switch statement default,
|
// There exists a 10th JSON style used as the switch statement default,
|
||||||
// but visually it is identical to 60fps one second.
|
// but visually it is identical to 60fps one second.
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -75,12 +76,30 @@ public class ThemePatch extends BaseThemePatch {
|
|||||||
return Settings.GRADIENT_LOADING_SCREEN.get();
|
return Settings.GRADIENT_LOADING_SCREEN.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean showSplashScreen(boolean original) {
|
||||||
|
return Settings.SPLASH_SCREEN_ANIMATION_STYLE.get() != SplashScreenAnimationStyle.DISABLED && original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int showSplashScreen(int i, int i2) {
|
||||||
|
if (Settings.SPLASH_SCREEN_ANIMATION_STYLE.get() != SplashScreenAnimationStyle.DISABLED || i != i2) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getLoadingScreenType(int original) {
|
public static int getLoadingScreenType(int original) {
|
||||||
SplashScreenAnimationStyle style = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
|
SplashScreenAnimationStyle style = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
|
||||||
if (style == SplashScreenAnimationStyle.DEFAULT) {
|
|
||||||
|
if (style == SplashScreenAnimationStyle.DISABLED) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -42,7 +42,7 @@ import app.revanced.extension.shared.Logger;
|
|||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.ui.Dim;
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeAPI;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ public class ReturnYouTubeDislike {
|
|||||||
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached lookup of all video ids.
|
* Cached lookup of all video IDs.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
private static final Map<String, ReturnYouTubeDislike> fetchCache = new HashMap<>();
|
private static final Map<String, ReturnYouTubeDislike> fetchCache = new HashMap<>();
|
||||||
@@ -206,8 +206,8 @@ public class ReturnYouTubeDislike {
|
|||||||
return newSpannableWithDislikes(oldSpannable, voteData);
|
return newSpannableWithDislikes(oldSpannable, voteData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Some locales use right to left layout (Arabic, Hebrew, etc).
|
// Note: Some locales use right to left layout (Arabic, Hebrew, etc.).
|
||||||
// If making changes to this code, change device settings to a RTL language and verify layout is correct.
|
// If making changes to this code, change device settings to an RTL language and verify layout is correct.
|
||||||
CharSequence oldLikes = oldSpannable;
|
CharSequence oldLikes = oldSpannable;
|
||||||
|
|
||||||
// YouTube creators can hide the like count on a video,
|
// YouTube creators can hide the like count on a video,
|
||||||
@@ -419,7 +419,7 @@ public class ReturnYouTubeDislike {
|
|||||||
private ReturnYouTubeDislike(@NonNull String videoId) {
|
private ReturnYouTubeDislike(@NonNull String videoId) {
|
||||||
this.videoId = Objects.requireNonNull(videoId);
|
this.videoId = Objects.requireNonNull(videoId);
|
||||||
this.timeFetched = System.currentTimeMillis();
|
this.timeFetched = System.currentTimeMillis();
|
||||||
this.future = Utils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
this.future = Utils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeAPI.fetchVotes(videoId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isExpired(long now) {
|
private boolean isExpired(long now) {
|
||||||
@@ -512,7 +512,7 @@ public class ReturnYouTubeDislike {
|
|||||||
if (votingData == null) {
|
if (votingData == null) {
|
||||||
// Method automatically prevents showing multiple toasts if the connection failed.
|
// Method automatically prevents showing multiple toasts if the connection failed.
|
||||||
// This call is needed here in case the api call did succeed but took too long.
|
// This call is needed here in case the api call did succeed but took too long.
|
||||||
ReturnYouTubeDislikeApi.handleConnectionError(
|
ReturnYouTubeDislikeAPI.handleConnectionError(
|
||||||
str("revanced_ryd_failure_connection_timeout"),
|
str("revanced_ryd_failure_connection_timeout"),
|
||||||
null, null, Toast.LENGTH_SHORT);
|
null, null, Toast.LENGTH_SHORT);
|
||||||
Logger.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
Logger.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
||||||
@@ -549,7 +549,7 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scrolling Shorts does not cause the Spans to be reloaded,
|
// Scrolling Shorts does not cause the Spans to be reloaded,
|
||||||
// so there is no need to cache the likes for this situations.
|
// so there is no need to cache the likes for these situations.
|
||||||
Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId);
|
Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId);
|
||||||
return newSpannableWithLikes(original, votingData);
|
return newSpannableWithLikes(original, votingData);
|
||||||
}
|
}
|
||||||
@@ -601,7 +601,7 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
voteSerialExecutor.execute(() -> {
|
voteSerialExecutor.execute(() -> {
|
||||||
try { // Must wrap in try/catch to properly log exceptions.
|
try { // Must wrap in try/catch to properly log exceptions.
|
||||||
ReturnYouTubeDislikeApi.sendVote(videoId, vote);
|
ReturnYouTubeDislikeAPI.sendVote(videoId, vote);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Failed to send vote", ex);
|
Logger.printException(() -> "Failed to send vote", ex);
|
||||||
}
|
}
|
||||||
@@ -675,7 +675,7 @@ class VerticallyCenteredImageSpan extends ImageSpan {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useOriginalWidth Use the original layout width of the text this span is applied to,
|
* @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 not the bounds of the Drawable. Drawable is always displayed using its own bounds,
|
||||||
* and this setting only affects the layout width of the entire span.
|
* and this setting only affects the layout width of the entire span.
|
||||||
*/
|
*/
|
||||||
public VerticallyCenteredImageSpan(Drawable drawable, boolean useOriginalWidth) {
|
public VerticallyCenteredImageSpan(Drawable drawable, boolean useOriginalWidth) {
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ public final class RYDVoteData {
|
|||||||
private volatile long likeCount; // Read/write from different threads.
|
private volatile long likeCount; // Read/write from different threads.
|
||||||
/**
|
/**
|
||||||
* Like count can be hidden by video creator, but RYD still tracks the number
|
* Like count can be hidden by video creator, but RYD still tracks the number
|
||||||
* of like/dislikes it received thru it's browser extension and and API.
|
* of like/dislikes it received through its browser extension and API.
|
||||||
* The raw like/dislikes can be used to calculate a percentage.
|
* The raw like/dislikes can be used to calculate a percentage.
|
||||||
*
|
*
|
||||||
* Raw values can be null, especially for older videos with little to no views.
|
* Raw values can be null, especially for older videos with little to no views.
|
||||||
@@ -74,7 +74,7 @@ public final class RYDVoteData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public like count of the video, as reported by YT when RYD last updated it's data.
|
* Public like count of the video, as reported by YT when RYD last updated its data.
|
||||||
*
|
*
|
||||||
* If the likes were hidden by the video creator, then this returns an
|
* If the likes were hidden by the video creator, then this returns an
|
||||||
* estimated likes using the same extrapolation as the dislikes.
|
* estimated likes using the same extrapolation as the dislikes.
|
||||||
|
|||||||
+32
-32
@@ -28,7 +28,7 @@ import app.revanced.extension.shared.requests.Requester;
|
|||||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
public class ReturnYouTubeDislikeApi {
|
public class ReturnYouTubeDislikeAPI {
|
||||||
/**
|
/**
|
||||||
* {@link #fetchVotes(String)} TCP connection timeout.
|
* {@link #fetchVotes(String)} TCP connection timeout.
|
||||||
*/
|
*/
|
||||||
@@ -43,7 +43,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
/**
|
/**
|
||||||
* Default connection and response timeout for voting and registration.
|
* Default connection and response timeout for voting and registration.
|
||||||
*
|
*
|
||||||
* Voting and user registration runs in the background and has has no urgency
|
* Voting and user registration runs in the background and has no urgency
|
||||||
* so this can be a larger value.
|
* so this can be a larger value.
|
||||||
*/
|
*/
|
||||||
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 60 * 1000; // 60 Seconds.
|
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 60 * 1000; // 60 Seconds.
|
||||||
@@ -109,7 +109,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Total time spent waiting for {@link #fetchVotes(String)} network call to complete.
|
* Total time spent waiting for {@link #fetchVotes(String)} network call to complete.
|
||||||
* Value does does not persist on app shut down.
|
* Value does not persist on app shut down.
|
||||||
*/
|
*/
|
||||||
private static volatile long fetchCallResponseTimeTotal;
|
private static volatile long fetchCallResponseTimeTotal;
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
return numberOfRateLimitRequestsEncountered;
|
return numberOfRateLimitRequestsEncountered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReturnYouTubeDislikeApi() {
|
private ReturnYouTubeDislikeAPI() {
|
||||||
} // utility class
|
} // utility class
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -251,7 +251,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
if (!lastApiCallFailed && Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) {
|
if (!lastApiCallFailed && Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) {
|
||||||
if (responseCode != null && responseCode == HTTP_STATUS_CODE_UNAUTHORIZED) {
|
if (responseCode != null && responseCode == HTTP_STATUS_CODE_UNAUTHORIZED) {
|
||||||
Logger.printInfo(() -> "Ignoring status code " + HTTP_STATUS_CODE_UNAUTHORIZED
|
Logger.printInfo(() -> "Ignoring status code " + HTTP_STATUS_CODE_UNAUTHORIZED
|
||||||
+ " (API authorization erorr)");
|
+ " (API authorization error)");
|
||||||
return; // Do not set api failure field.
|
return; // Do not set api failure field.
|
||||||
} else if (toastDuration != null) {
|
} else if (toastDuration != null) {
|
||||||
Utils.showToast(toastMessage, toastDuration);
|
Utils.showToast(toastMessage, toastDuration);
|
||||||
@@ -281,7 +281,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
// request headers, as per https://returnyoutubedislike.com/docs/fetching
|
// request headers, as per https://returnyoutubedislike.com/docs/fetching
|
||||||
// the documentation says to use 'Accept:text/html', but the RYD browser plugin uses 'Accept:application/json'
|
// the documentation says to use 'Accept:text/html', but the RYD browser plugin uses 'Accept:application/json'
|
||||||
connection.setRequestProperty("Accept", "application/json");
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
connection.setRequestProperty("Connection", "keep-alive"); // keep-alive is on by default with http 1.1, but specify anyways
|
connection.setRequestProperty("Connection", "keep-alive"); // keep-alive is on by default with http 1.1, but specify anyway
|
||||||
connection.setRequestProperty("Pragma", "no-cache");
|
connection.setRequestProperty("Pragma", "no-cache");
|
||||||
connection.setRequestProperty("Cache-Control", "no-cache");
|
connection.setRequestProperty("Cache-Control", "no-cache");
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
@@ -306,7 +306,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
Logger.printDebug(() -> "Voting data fetched: " + votingData);
|
Logger.printDebug(() -> "Voting data fetched: " + votingData);
|
||||||
return votingData;
|
return votingData;
|
||||||
} catch (JSONException ex) {
|
} catch (JSONException ex) {
|
||||||
Logger.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
Logger.printException(() -> "Failed to parse video: " + videoId + " JSON: " + json, ex);
|
||||||
// fall thru to update statistics
|
// fall thru to update statistics
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -329,7 +329,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The newly created and registered user id. Returns NULL if registration failed.
|
* @return The newly created and registered user ID. Returns NULL if registration failed.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String registerAsNewUser() {
|
public static String registerAsNewUser() {
|
||||||
@@ -338,10 +338,10 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
if (checkIfRateLimitInEffect("registerAsNewUser")) {
|
if (checkIfRateLimitInEffect("registerAsNewUser")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String userId = randomString(36);
|
String userID = randomString(36);
|
||||||
Logger.printDebug(() -> "Trying to register new user");
|
Logger.printDebug(() -> "Trying to register new user");
|
||||||
|
|
||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userId);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userID);
|
||||||
connection.setRequestProperty("Accept", "application/json");
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
connection.setConnectTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
connection.setConnectTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
||||||
connection.setReadTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
connection.setReadTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
||||||
@@ -357,7 +357,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
int difficulty = json.getInt("difficulty");
|
int difficulty = json.getInt("difficulty");
|
||||||
|
|
||||||
String solution = solvePuzzle(challenge, difficulty);
|
String solution = solvePuzzle(challenge, difficulty);
|
||||||
return confirmRegistration(userId, solution);
|
return confirmRegistration(userID, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||||
@@ -374,9 +374,9 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String confirmRegistration(String userId, String solution) {
|
private static String confirmRegistration(String userID, String solution) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
Objects.requireNonNull(userId);
|
Objects.requireNonNull(userID);
|
||||||
Objects.requireNonNull(solution);
|
Objects.requireNonNull(solution);
|
||||||
try {
|
try {
|
||||||
if (checkIfRateLimitInEffect("confirmRegistration")) {
|
if (checkIfRateLimitInEffect("confirmRegistration")) {
|
||||||
@@ -384,7 +384,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
Logger.printDebug(() -> "Trying to confirm registration with solution: " + solution);
|
Logger.printDebug(() -> "Trying to confirm registration with solution: " + solution);
|
||||||
|
|
||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_REGISTRATION, userId);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_REGISTRATION, userID);
|
||||||
applyCommonPostRequestSettings(connection);
|
applyCommonPostRequestSettings(connection);
|
||||||
|
|
||||||
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
||||||
@@ -401,12 +401,12 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
Logger.printDebug(() -> "Registration confirmation successful");
|
Logger.printDebug(() -> "Registration confirmation successful");
|
||||||
return userId;
|
return userID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Something went wrong, might as well disconnect.
|
// Something went wrong, might as well disconnect.
|
||||||
String response = Requester.parseStringAndDisconnect(connection);
|
String response = Requester.parseStringAndDisconnect(connection);
|
||||||
Logger.printInfo(() -> "Failed to confirm registration for user: " + userId
|
Logger.printInfo(() -> "Failed to confirm registration for user: " + userID
|
||||||
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "''");
|
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "''");
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||||
responseCode, null, Toast.LENGTH_LONG);
|
responseCode, null, Toast.LENGTH_LONG);
|
||||||
@@ -416,7 +416,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
handleConnectionError(str("revanced_ryd_failure_generic", "confirm registration failed"),
|
handleConnectionError(str("revanced_ryd_failure_generic", "confirm registration failed"),
|
||||||
null, ex, Toast.LENGTH_LONG);
|
null, ex, Toast.LENGTH_LONG);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Failed to confirm registration for user: " + userId
|
Logger.printException(() -> "Failed to confirm registration for user: " + userID
|
||||||
+ "solution: " + solution, ex);
|
+ "solution: " + solution, ex);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -429,19 +429,19 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
* and the network call fails, this returns NULL.
|
* and the network call fails, this returns NULL.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getUserId() {
|
private static String getUserID() {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
|
|
||||||
String userId = Settings.RYD_USER_ID.get();
|
String userID = Settings.RYD_USER_ID.get();
|
||||||
if (!userId.isEmpty()) {
|
if (!userID.isEmpty()) {
|
||||||
return userId;
|
return userID;
|
||||||
}
|
}
|
||||||
|
|
||||||
userId = registerAsNewUser();
|
userID = registerAsNewUser();
|
||||||
if (userId != null) {
|
if (userID != null) {
|
||||||
Settings.RYD_USER_ID.save(userId);
|
Settings.RYD_USER_ID.save(userID);
|
||||||
}
|
}
|
||||||
return userId;
|
return userID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean sendVote(String videoId, ReturnYouTubeDislike.Vote vote) {
|
public static boolean sendVote(String videoId, ReturnYouTubeDislike.Vote vote) {
|
||||||
@@ -450,8 +450,8 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
Objects.requireNonNull(vote);
|
Objects.requireNonNull(vote);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String userId = getUserId();
|
String userID = getUserID();
|
||||||
if (userId == null) return false;
|
if (userID == null) return false;
|
||||||
|
|
||||||
if (checkIfRateLimitInEffect("sendVote")) {
|
if (checkIfRateLimitInEffect("sendVote")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -461,7 +461,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.SEND_VOTE);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.SEND_VOTE);
|
||||||
applyCommonPostRequestSettings(connection);
|
applyCommonPostRequestSettings(connection);
|
||||||
|
|
||||||
String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote.value + "\"}";
|
String voteJsonString = "{\"userId\": \"" + userID + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote.value + "\"}";
|
||||||
byte[] body = voteJsonString.getBytes(StandardCharsets.UTF_8);
|
byte[] body = voteJsonString.getBytes(StandardCharsets.UTF_8);
|
||||||
connection.setFixedLengthStreamingMode(body.length);
|
connection.setFixedLengthStreamingMode(body.length);
|
||||||
try (OutputStream os = connection.getOutputStream()) {
|
try (OutputStream os = connection.getOutputStream()) {
|
||||||
@@ -479,7 +479,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
int difficulty = json.getInt("difficulty");
|
int difficulty = json.getInt("difficulty");
|
||||||
|
|
||||||
String solution = solvePuzzle(challenge, difficulty);
|
String solution = solvePuzzle(challenge, difficulty);
|
||||||
return confirmVote(videoId, userId, solution);
|
return confirmVote(videoId, userID, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
||||||
@@ -498,10 +498,10 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean confirmVote(String videoId, String userId, String solution) {
|
private static boolean confirmVote(String videoId, String userID, String solution) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
Objects.requireNonNull(userId);
|
Objects.requireNonNull(userID);
|
||||||
Objects.requireNonNull(solution);
|
Objects.requireNonNull(solution);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -512,7 +512,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_VOTE);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_VOTE);
|
||||||
applyCommonPostRequestSettings(connection);
|
applyCommonPostRequestSettings(connection);
|
||||||
|
|
||||||
String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
String jsonInputString = "{\"userId\": \"" + userID + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
||||||
byte[] body = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
byte[] body = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||||
connection.setFixedLengthStreamingMode(body.length);
|
connection.setFixedLengthStreamingMode(body.length);
|
||||||
try (OutputStream os = connection.getOutputStream()) {
|
try (OutputStream os = connection.getOutputStream()) {
|
||||||
+3
-3
@@ -3,15 +3,15 @@ package app.revanced.extension.youtube.returnyoutubedislike.ui;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows tapping the RYD about preference to open the website.
|
* Allows tapping the RYD about preference to open the website.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ReturnYouTubeDislikeAboutPreference extends UrlLinkPreference {
|
public class ReturnYouTubeDislikeAboutPreference extends URLLinkPreference {
|
||||||
{
|
{
|
||||||
externalUrl = "https://returnyoutubedislike.com";
|
externalURL = "https://returnyoutubedislike.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
|||||||
+9
-9
@@ -11,7 +11,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeAPI;
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends PreferenceCategory {
|
public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends PreferenceCategory {
|
||||||
@@ -63,22 +63,22 @@ public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends Preference
|
|||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getFetchCallResponseTimeAverage_title",
|
"revanced_ryd_statistics_getFetchCallResponseTimeAverage_title",
|
||||||
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage())
|
createMillisecondStringFromNumber(ReturnYouTubeDislikeAPI.getFetchCallResponseTimeAverage())
|
||||||
);
|
);
|
||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getFetchCallResponseTimeMin_title",
|
"revanced_ryd_statistics_getFetchCallResponseTimeMin_title",
|
||||||
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin())
|
createMillisecondStringFromNumber(ReturnYouTubeDislikeAPI.getFetchCallResponseTimeMin())
|
||||||
);
|
);
|
||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getFetchCallResponseTimeMax_title",
|
"revanced_ryd_statistics_getFetchCallResponseTimeMax_title",
|
||||||
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax())
|
createMillisecondStringFromNumber(ReturnYouTubeDislikeAPI.getFetchCallResponseTimeMax())
|
||||||
);
|
);
|
||||||
|
|
||||||
String fetchCallTimeWaitingLastSummary;
|
String fetchCallTimeWaitingLastSummary;
|
||||||
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
|
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeAPI.getFetchCallResponseTimeLast();
|
||||||
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
|
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeAPI.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
|
||||||
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
|
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
|
||||||
} else {
|
} else {
|
||||||
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
|
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
|
||||||
@@ -90,7 +90,7 @@ public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends Preference
|
|||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getFetchCallCount_title",
|
"revanced_ryd_statistics_getFetchCallCount_title",
|
||||||
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
|
createSummaryText(ReturnYouTubeDislikeAPI.getFetchCallCount(),
|
||||||
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
|
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
|
||||||
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"
|
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"
|
||||||
)
|
)
|
||||||
@@ -98,7 +98,7 @@ public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends Preference
|
|||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_title",
|
"revanced_ryd_statistics_getFetchCallNumberOfFailures_title",
|
||||||
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
|
createSummaryText(ReturnYouTubeDislikeAPI.getFetchCallNumberOfFailures(),
|
||||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
|
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
|
||||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"
|
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"
|
||||||
)
|
)
|
||||||
@@ -106,7 +106,7 @@ public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends Preference
|
|||||||
|
|
||||||
addStatisticPreference(
|
addStatisticPreference(
|
||||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title",
|
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title",
|
||||||
createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
|
createSummaryText(ReturnYouTubeDislikeAPI.getNumberOfRateLimitRequestsEncountered(),
|
||||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
|
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
|
||||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"
|
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"
|
||||||
)
|
)
|
||||||
|
|||||||
+51
-27
@@ -18,7 +18,6 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerH
|
|||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||||
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
|
||||||
import static app.revanced.extension.youtube.patches.litho.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
import static app.revanced.extension.youtube.patches.litho.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch.SpoofClientAv1Availability;
|
import static app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch.SpoofClientAv1Availability;
|
||||||
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
||||||
@@ -56,6 +55,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||||
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message");
|
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message");
|
||||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_PREMIUM_VIDEO_QUALITY = new BooleanSetting("revanced_hide_premium_video_quality", TRUE, true);
|
||||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
||||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
||||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
||||||
@@ -80,16 +80,15 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
||||||
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_PLAYER_POPUP_ADS = new BooleanSetting("revanced_hide_player_popup_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
||||||
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
||||||
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
||||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_VIEW_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_view_products_banner", TRUE);
|
public static final BooleanSetting HIDE_VIEW_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_view_products_banner", TRUE);
|
||||||
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
public static final BooleanSetting HIDE_YOUTUBE_PREMIUM_PROMOTIONS = new BooleanSetting("revanced_hide_youtube_premium_promotions", TRUE);
|
||||||
|
|
||||||
// Feed
|
// Feed
|
||||||
public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true);
|
public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true);
|
||||||
@@ -97,8 +96,10 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_CHIPS_SHELF = new BooleanSetting("revanced_hide_chips_shelf", TRUE);
|
public static final BooleanSetting HIDE_CHIPS_SHELF = new BooleanSetting("revanced_hide_chips_shelf", TRUE);
|
||||||
public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE);
|
public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
||||||
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message");
|
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE);
|
public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_FEED_FLYOUT_MENU = new BooleanSetting("revanced_hide_feed_flyout_menu", FALSE);
|
||||||
|
public static final StringSetting HIDE_FEED_FLYOUT_MENU_FILTER_STRINGS = new StringSetting("revanced_hide_feed_flyout_menu_filter_strings", "", true, parent(HIDE_FEED_FLYOUT_MENU));
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_HISTORY = new BooleanSetting("revanced_hide_filter_bar_feed_in_history", FALSE);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_HISTORY = new BooleanSetting("revanced_hide_filter_bar_feed_in_history", FALSE);
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS = new BooleanSetting("revanced_hide_filter_bar_feed_in_related_videos", FALSE, true);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS = new BooleanSetting("revanced_hide_filter_bar_feed_in_related_videos", FALSE, true);
|
||||||
@@ -106,16 +107,21 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_FLOATING_MICROPHONE_BUTTON = new BooleanSetting("revanced_hide_floating_microphone_button", TRUE, true);
|
public static final BooleanSetting HIDE_FLOATING_MICROPHONE_BUTTON = new BooleanSetting("revanced_hide_floating_microphone_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
||||||
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_LATEST_VIDEOS_BUTTON = new BooleanSetting("revanced_hide_latest_videos_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||||
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_SUBSCRIBED_CHANNELS_BAR = new BooleanSetting("revanced_hide_subscribed_channels_bar", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE);
|
public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE);
|
||||||
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
||||||
public static final BooleanSetting HIDE_UPLOAD_TIME = new BooleanSetting("revanced_hide_upload_time", FALSE, "revanced_hide_upload_time_user_dialog_message");
|
public static final BooleanSetting HIDE_UPLOAD_TIME = new BooleanSetting("revanced_hide_upload_time", FALSE, "revanced_hide_upload_time_user_dialog_message");
|
||||||
public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE);
|
public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE);
|
||||||
public static final BooleanSetting HIDE_VIEW_COUNT = new BooleanSetting("revanced_hide_view_count", FALSE, "revanced_hide_view_count_user_dialog_message");
|
public static final BooleanSetting HIDE_VIEW_COUNT = new BooleanSetting("revanced_hide_view_count", FALSE, "revanced_hide_view_count_user_dialog_message");
|
||||||
|
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_YOU_MAY_LIKE_SECTION = new BooleanSetting("revanced_hide_you_may_like_section", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_VISUAL_SPACER = new BooleanSetting("revanced_hide_visual_spacer", TRUE);
|
public static final BooleanSetting HIDE_VISUAL_SPACER = new BooleanSetting("revanced_hide_visual_spacer", TRUE);
|
||||||
|
|
||||||
// Alternative thumbnails
|
// Alternative thumbnails
|
||||||
@@ -138,6 +144,8 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||||
|
|
||||||
// Channel page
|
// Channel page
|
||||||
|
public static final BooleanSetting HIDE_CHANNEL_TAB = new BooleanSetting("revanced_hide_channel_tab", FALSE);
|
||||||
|
public static final StringSetting HIDE_CHANNEL_TAB_FILTER_STRINGS = new StringSetting("revanced_hide_channel_tab_filter_strings", "", true, parent(HIDE_CHANNEL_TAB));
|
||||||
public static final BooleanSetting HIDE_COMMUNITY_BUTTON = new BooleanSetting("revanced_hide_community_button", TRUE);
|
public static final BooleanSetting HIDE_COMMUNITY_BUTTON = new BooleanSetting("revanced_hide_community_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", FALSE);
|
public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", FALSE);
|
||||||
public static final BooleanSetting HIDE_JOIN_BUTTON = new BooleanSetting("revanced_hide_join_button", FALSE);
|
public static final BooleanSetting HIDE_JOIN_BUTTON = new BooleanSetting("revanced_hide_join_button", FALSE);
|
||||||
@@ -151,21 +159,31 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||||
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
||||||
public static final BooleanSetting DISABLE_CHAPTER_SKIP_DOUBLE_TAP = new BooleanSetting("revanced_disable_chapter_skip_double_tap", FALSE);
|
public static final BooleanSetting DISABLE_CHAPTER_SKIP_DOUBLE_TAP = new BooleanSetting("revanced_disable_chapter_skip_double_tap", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO = new BooleanSetting("revanced_disable_haptic_feedback_seek_undo", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_TAP_AND_HOLD = new BooleanSetting("revanced_disable_haptic_feedback_tap_and_hold", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_ZOOM = new BooleanSetting("revanced_disable_haptic_feedback_zoom", FALSE);
|
||||||
|
public static final BooleanSetting DISABLE_PLAYER_POPUP_PANELS = new BooleanSetting("revanced_disable_player_popup_panels", FALSE);
|
||||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||||
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||||
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_AUTOPLAY_PREVIEW = new BooleanSetting("revanced_hide_autoplay_preview", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_COLLAPSE_BUTTON = new BooleanSetting("revanced_hide_collapse_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
||||||
public static final BooleanSetting HIDE_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
|
public static final BooleanSetting HIDE_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
|
||||||
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
||||||
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
|
public static final BooleanSetting HIDE_END_SCREEN_CARDS = new BooleanSetting("revanced_hide_end_screen_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
|
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_FULLSCREEN_BUTTON = new BooleanSetting("revanced_hide_fullscreen_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
||||||
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE, parentNot(HIDE_CHANNEL_BAR));
|
||||||
|
public static final BooleanSetting HIDE_LIVE_CHAT_REPLAY_BUTTON = new BooleanSetting("revanced_hide_live_chat_replay_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true);
|
public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS = new BooleanSetting("revanced_hide_player_previous_next_buttons", FALSE, true);
|
public static final BooleanSetting HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS = new BooleanSetting("revanced_hide_player_previous_next_buttons", FALSE, true);
|
||||||
@@ -174,11 +192,11 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
||||||
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
||||||
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_VIDEO_TITLE = new BooleanSetting("revanced_hide_video_title", FALSE);
|
||||||
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
|
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
|
||||||
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
||||||
public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE);
|
public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE);
|
||||||
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
||||||
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
|
||||||
|
|
||||||
// Miniplayer
|
// Miniplayer
|
||||||
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
||||||
@@ -208,6 +226,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_EMOJI_AND_TIMESTAMP_BUTTONS = new BooleanSetting("revanced_hide_comments_emoji_and_timestamp_buttons", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_EMOJI_AND_TIMESTAMP_BUTTONS = new BooleanSetting("revanced_hide_comments_emoji_and_timestamp_buttons", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_COMMENTS_SECTION_IN_HOME_FEED = new BooleanSetting("revanced_hide_comments_section_in_home_feed", FALSE, parentNot(HIDE_COMMENTS_SECTION));
|
||||||
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
@@ -215,6 +234,12 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
|
public static final BooleanSetting HIDE_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_COURSE_PROGRESS_SECTION = new BooleanSetting("revanced_hide_course_progress_section", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_EXPLORE_SECTION = new BooleanSetting("revanced_hide_explore_section", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_EXPLORE_COURSE_SECTION = new BooleanSetting("revanced_hide_explore_course_section", FALSE, parentNot(HIDE_EXPLORE_SECTION));
|
||||||
|
public static final BooleanSetting HIDE_EXPLORE_PODCAST_SECTION = new BooleanSetting("revanced_hide_explore_podcast_section", FALSE, parentNot(HIDE_EXPLORE_SECTION));
|
||||||
|
public static final BooleanSetting HIDE_FEATURED_PLACES_SECTION = new BooleanSetting("revanced_hide_featured_places_section", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_GAMING_SECTION = new BooleanSetting("revanced_hide_gaming_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_HYPE_POINTS = new BooleanSetting("revanced_hide_hype_points", FALSE);
|
public static final BooleanSetting HIDE_HYPE_POINTS = new BooleanSetting("revanced_hide_hype_points", FALSE);
|
||||||
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
||||||
@@ -222,14 +247,15 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_FEATURED_VIDEOS_SECTION = new BooleanSetting("revanced_hide_featured_videos_section", FALSE, parentNot(HIDE_INFO_CARDS_SECTION));
|
public static final BooleanSetting HIDE_FEATURED_VIDEOS_SECTION = new BooleanSetting("revanced_hide_featured_videos_section", FALSE, parentNot(HIDE_INFO_CARDS_SECTION));
|
||||||
public static final BooleanSetting HIDE_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_subscribe_button", FALSE, parentNot(HIDE_INFO_CARDS_SECTION));
|
public static final BooleanSetting HIDE_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_subscribe_button", FALSE, parentNot(HIDE_INFO_CARDS_SECTION));
|
||||||
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
public static final BooleanSetting HIDE_MUSIC_SECTION = new BooleanSetting("revanced_hide_music_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_QUIZZES_SECTION = new BooleanSetting("revanced_hide_quizzes_section", FALSE);
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
||||||
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", FALSE, "revanced_hide_clip_button_user_dialog_message");
|
||||||
public static final BooleanSetting HIDE_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_comments_button", TRUE);
|
public static final BooleanSetting HIDE_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_comments_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE);
|
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||||
@@ -256,7 +282,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_STABLE_VOLUME = new BooleanSetting("revanced_hide_player_flyout_stable_volume", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_STABLE_VOLUME = new BooleanSetting("revanced_hide_player_flyout_stable_volume", FALSE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_player_flyout_video_quality_footer", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_player_flyout_video_quality_footer", FALSE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY = new BooleanSetting("revanced_hide_player_flyout_video_quality", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY = new BooleanSetting("revanced_hide_player_flyout_video_quality", FALSE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", FALSE);
|
||||||
|
|
||||||
// General layout
|
// General layout
|
||||||
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
|
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
|
||||||
@@ -265,22 +291,22 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||||
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("revanced_splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
|
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("revanced_splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
|
||||||
public static final EnumSetting<HeaderLogo> HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true);
|
public static final EnumSetting<HeaderLogo> HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true);
|
||||||
public static final BooleanSetting DISABLE_SIGNIN_TO_TV_POPUP = new BooleanSetting("revanced_disable_signin_to_tv_popup", FALSE);
|
public static final BooleanSetting DISABLE_SIGN_IN_TO_TV_POPUP = new BooleanSetting("revanced_disable_sign_in_to_tv_popup", FALSE);
|
||||||
|
|
||||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
|
||||||
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
|
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
|
||||||
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
||||||
new ChangeStartPageTypeAvailability());
|
new ChangeStartPageTypeAvailability());
|
||||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
|
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.35.36", true, parent(SPOOF_APP_VERSION));
|
||||||
// Navigation buttons
|
// Navigation buttons
|
||||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
|
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_NAVIGATION_BUTTON_LABELS = new BooleanSetting("revanced_hide_navigation_button_labels", FALSE, true);
|
public static final BooleanSetting HIDE_NAVIGATION_BUTTON_LABELS = new BooleanSetting("revanced_hide_navigation_button_labels", FALSE, true);
|
||||||
|
public static final BooleanSetting NARROW_NAVIGATION_BUTTONS = new BooleanSetting("revanced_narrow_navigation_buttons", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
|
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
|
||||||
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
|
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
|
||||||
"revanced_switch_create_with_notifications_button_user_dialog_message");
|
"revanced_switch_create_with_notifications_button_user_dialog_message");
|
||||||
@@ -290,11 +316,19 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
|
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
|
||||||
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
|
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
public static final BooleanSetting HIDE_TOOLBAR_CREATE_BUTTON = new BooleanSetting("revanced_hide_toolbar_create_button", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_TOOLBAR_NOTIFICATION_BUTTON = new BooleanSetting("revanced_hide_toolbar_notification_button", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_TOOLBAR_SEARCH_BUTTON = new BooleanSetting("revanced_hide_toolbar_search_button", FALSE, true);
|
||||||
|
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||||
|
|
||||||
// Shorts
|
// Shorts
|
||||||
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
||||||
public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE);
|
public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE);
|
||||||
public static final EnumSetting<ShortsPlayerType> SHORTS_PLAYER_TYPE = new EnumSetting<>("revanced_shorts_player_type", ShortsPlayerType.SHORTS_PLAYER);
|
public static final EnumSetting<ShortsPlayerType> SHORTS_PLAYER_TYPE = new EnumSetting<>("revanced_shorts_player_type", ShortsPlayerType.SHORTS_PLAYER);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_AI_BUTTON = new BooleanSetting("revanced_hide_shorts_ai_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_AUTO_DUBBED_LABEL = new BooleanSetting("revanced_hide_shorts_auto_dubbed_label", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_AUTO_DUBBED_LABEL = new BooleanSetting("revanced_hide_shorts_auto_dubbed_label", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_CHANNEL = new BooleanSetting("revanced_hide_shorts_channel", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
||||||
@@ -330,6 +364,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_UPCOMING_BUTTON = new BooleanSetting("revanced_hide_shorts_upcoming_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_UPCOMING_BUTTON = new BooleanSetting("revanced_hide_shorts_upcoming_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_USE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_use_sound_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_USE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_use_sound_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_USE_TEMPLATE_BUTTON = new BooleanSetting("revanced_hide_shorts_use_template_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_USE_TEMPLATE_BUTTON = new BooleanSetting("revanced_hide_shorts_use_template_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_VIDEO_DESCRIPTION = new BooleanSetting("revanced_hide_shorts_video_description", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
||||||
public static final BooleanSetting SHORTS_AUTOPLAY = new BooleanSetting("revanced_shorts_autoplay", FALSE);
|
public static final BooleanSetting SHORTS_AUTOPLAY = new BooleanSetting("revanced_shorts_autoplay", FALSE);
|
||||||
public static final BooleanSetting SHORTS_AUTOPLAY_BACKGROUND = new BooleanSetting("revanced_shorts_autoplay_background", TRUE);
|
public static final BooleanSetting SHORTS_AUTOPLAY_BACKGROUND = new BooleanSetting("revanced_shorts_autoplay_background", TRUE);
|
||||||
@@ -340,11 +375,8 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
|
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
|
||||||
public static final BooleanSetting FULLSCREEN_LARGE_SEEKBAR = new BooleanSetting("revanced_fullscreen_large_seekbar", FALSE);
|
public static final BooleanSetting FULLSCREEN_LARGE_SEEKBAR = new BooleanSetting("revanced_fullscreen_large_seekbar", FALSE);
|
||||||
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
|
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
|
||||||
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
|
||||||
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", FALSE);
|
|
||||||
public static final BooleanSetting SEEKBAR_THUMBNAILS_HIGH_QUALITY = new BooleanSetting("revanced_seekbar_thumbnails_high_quality", FALSE, true,
|
|
||||||
"revanced_seekbar_thumbnails_high_quality_dialog_message", new SeekbarThumbnailsHighQualityAvailability());
|
|
||||||
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE, true);
|
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE, true);
|
||||||
|
public static final BooleanSetting TAP_TO_SEEK = new BooleanSetting("revanced_tap_to_seek", FALSE);
|
||||||
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
@@ -356,10 +388,6 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE);
|
public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE);
|
||||||
public static final BooleanSetting PAUSE_ON_AUDIO_INTERRUPT = new BooleanSetting("revanced_pause_on_audio_interrupt", FALSE, true);
|
public static final BooleanSetting PAUSE_ON_AUDIO_INTERRUPT = new BooleanSetting("revanced_pause_on_audio_interrupt", FALSE, true);
|
||||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||||
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE);
|
|
||||||
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE);
|
|
||||||
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO = new BooleanSetting("revanced_disable_haptic_feedback_seek_undo", FALSE);
|
|
||||||
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_ZOOM = new BooleanSetting("revanced_disable_haptic_feedback_zoom", FALSE);
|
|
||||||
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
|
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
|
||||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
||||||
"revanced_spoof_device_dimensions_user_dialog_message");
|
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||||
@@ -536,10 +564,6 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||||||
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();
|
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
// RYD requires manually migrating old settings since the lack of
|
|
||||||
// a "revanced_" on the old setting causes duplicate key exceptions during export.
|
|
||||||
Setting.migrateFromOldPreferences(Setting.preferences, RYD_USER_ID, "ryd_user_id");
|
|
||||||
|
|
||||||
// Migrate old saved data. Must be done here before the settings can be used by any other code.
|
// Migrate old saved data. Must be done here before the settings can be used by any other code.
|
||||||
applyOldSbOpacityToColor(SB_CATEGORY_SPONSOR_COLOR, DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY);
|
applyOldSbOpacityToColor(SB_CATEGORY_SPONSOR_COLOR, DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY);
|
||||||
applyOldSbOpacityToColor(SB_CATEGORY_SELF_PROMO_COLOR, DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY);
|
applyOldSbOpacityToColor(SB_CATEGORY_SELF_PROMO_COLOR, DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY);
|
||||||
|
|||||||
+1
@@ -31,6 +31,7 @@ public class YouTubeActivityHook extends BaseActivityHook {
|
|||||||
|
|
||||||
private static final boolean USE_BOLD_ICONS = VersionCheckPatch.IS_20_31_OR_GREATER
|
private static final boolean USE_BOLD_ICONS = VersionCheckPatch.IS_20_31_OR_GREATER
|
||||||
&& !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
|
&& !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
|
||||||
|
&& !Settings.RESTORE_OLD_SETTINGS_MENUS.get()
|
||||||
&& (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get())
|
&& (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get())
|
||||||
> MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS;
|
> MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS;
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -2,15 +2,15 @@ package app.revanced.extension.youtube.settings.preference;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class AlternativeThumbnailsAboutDeArrowPreference extends UrlLinkPreference {
|
public class AlternativeThumbnailsAboutDeArrowPreference extends URLLinkPreference {
|
||||||
{
|
{
|
||||||
externalUrl = "https://dearrow.ajay.app";
|
externalURL = "https://dearrow.ajay.app";
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
|||||||
+1
-1
@@ -247,7 +247,7 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
|||||||
} else {
|
} else {
|
||||||
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||||
editText.setText(Downloader.findByPackageName(savedPackageName) == null
|
editText.setText(Downloader.findByPackageName(savedPackageName) == null
|
||||||
? savedPackageName // If the user is clicking thru options then retain existing other app.
|
? savedPackageName // If the user is clicking through options then retain existing other app.
|
||||||
: ""
|
: ""
|
||||||
);
|
);
|
||||||
editText.setEnabled(true); // Enable editing for Custom.
|
editText.setEnabled(true); // Enable editing for Custom.
|
||||||
|
|||||||
+6
-6
@@ -8,27 +8,27 @@ import android.text.Html;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows using basic html for the summary text.
|
* Allows using basic HTML for the summary text.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class HtmlPreference extends Preference {
|
public class HTMLPreference extends Preference {
|
||||||
{
|
{
|
||||||
setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT));
|
setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public HTMLPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
public HTMLPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HtmlPreference(Context context, AttributeSet attrs) {
|
public HTMLPreference(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HtmlPreference(Context context) {
|
public HTMLPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package app.revanced.extension.youtube.shared;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class EngagementPanel {
|
||||||
|
private static final AtomicReference<String> lastEngagementPanelId = new AtomicReference<>("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void close() {
|
||||||
|
String panelId = getId();
|
||||||
|
if (!panelId.isEmpty()) {
|
||||||
|
lastEngagementPanelId.set("");
|
||||||
|
Logger.printDebug(() -> "EngagementPanel closed, Last panel id: " + panelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void open(@Nullable String panelId) {
|
||||||
|
if (panelId != null && !panelId.isEmpty()) {
|
||||||
|
lastEngagementPanelId.set(panelId);
|
||||||
|
Logger.printDebug(() -> "EngagementPanel open, New panel id: " + panelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDescription() {
|
||||||
|
return getId().equals("video-description-ep-identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getId() {
|
||||||
|
return lastEngagementPanelId.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.youtube
|
package app.revanced.extension.youtube.shared
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger
|
import app.revanced.extension.shared.Logger
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
@@ -31,4 +31,4 @@ class Event<T> {
|
|||||||
observer.invoke(value)
|
observer.invoke(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -147,7 +147,7 @@ public final class NavigationBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.isCurrentlyOnMainThread()) {
|
if (Utils.isCurrentlyOnMainThread()) {
|
||||||
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
// The latch is released from the main thread, and waiting from the main thread will always time out.
|
||||||
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
||||||
// and for that use case the nav bar does not change so it's safe to return here.
|
// and for that use case the nav bar does not change so it's safe to return here.
|
||||||
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. " +
|
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. " +
|
||||||
@@ -307,7 +307,7 @@ public final class NavigationBar {
|
|||||||
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
|
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
|
||||||
/**
|
/**
|
||||||
* Create new video tab.
|
* Create new video tab.
|
||||||
* This tab will never be in a selected state, even if the create video UI is on screen.
|
* This tab will never be in a selected state, even if the Create video UI is on screen.
|
||||||
*/
|
*/
|
||||||
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
|
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.shared
|
package app.revanced.extension.youtube.shared
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger
|
import app.revanced.extension.shared.Logger
|
||||||
import app.revanced.extension.youtube.Event
|
import app.revanced.extension.youtube.shared.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PlayerControls visibility state.
|
* PlayerControls visibility state.
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ class PlayerControlsVisibilityObserverImpl(
|
|||||||
) : PlayerControlsVisibilityObserver {
|
) : PlayerControlsVisibilityObserver {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* id of the direct parent of controls_layout, R.id.youtube_controls_overlay
|
* ID of the direct parent of controls_layout, R.id.youtube_controls_overlay
|
||||||
*/
|
*/
|
||||||
private val controlsLayoutParentId =
|
private val controlsLayoutParentId =
|
||||||
Utils.getResourceIdentifier(activity, ResourceType.ID, "youtube_controls_overlay")
|
Utils.getResourceIdentifier(activity, ResourceType.ID, "youtube_controls_overlay")
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ package app.revanced.extension.youtube.shared
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import app.revanced.extension.youtube.Event
|
import app.revanced.extension.youtube.shared.Event
|
||||||
import app.revanced.extension.youtube.swipecontrols.misc.Rectangle
|
import app.revanced.extension.youtube.swipecontrols.misc.Rectangle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.shared
|
package app.revanced.extension.youtube.shared
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger
|
import app.revanced.extension.shared.Logger
|
||||||
import app.revanced.extension.youtube.Event
|
import app.revanced.extension.youtube.shared.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular player type.
|
* Regular player type.
|
||||||
@@ -107,7 +107,7 @@ enum class PlayerType {
|
|||||||
* Instead of this method, consider using {@link ShortsPlayerState}
|
* Instead of this method, consider using {@link ShortsPlayerState}
|
||||||
* which may work better for some situations.
|
* which may work better for some situations.
|
||||||
*
|
*
|
||||||
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
|
* @return If nothing, a Short, or a regular video is sliding off-screen to a dismissed or hidden state.
|
||||||
* @see ShortsPlayerState
|
* @see ShortsPlayerState
|
||||||
*/
|
*/
|
||||||
fun isNoneHiddenOrSlidingMinimized(): Boolean {
|
fun isNoneHiddenOrSlidingMinimized(): Boolean {
|
||||||
@@ -119,7 +119,7 @@ enum class PlayerType {
|
|||||||
* [NONE], [HIDDEN], [WATCH_WHILE_MINIMIZED], [WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED].
|
* [NONE], [HIDDEN], [WATCH_WHILE_MINIMIZED], [WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED].
|
||||||
*
|
*
|
||||||
* Useful to check if a Short is being played,
|
* Useful to check if a Short is being played,
|
||||||
* although will return false positive if a regular video is
|
* although it will return false positive if a regular video is
|
||||||
* opened and minimized (and a Short is not playing or being opened).
|
* opened and minimized (and a Short is not playing or being opened).
|
||||||
*
|
*
|
||||||
* Typically used to detect if a Short is playing when the player cannot be in a minimized state,
|
* Typically used to detect if a Short is playing when the player cannot be in a minimized state,
|
||||||
@@ -128,7 +128,7 @@ enum class PlayerType {
|
|||||||
* Instead of this method, consider using {@link ShortsPlayerState}
|
* Instead of this method, consider using {@link ShortsPlayerState}
|
||||||
* which may work better for some situations.
|
* which may work better for some situations.
|
||||||
*
|
*
|
||||||
* @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state,
|
* @return If nothing, a Short, a regular video is sliding off-screen to a dismissed or hidden state,
|
||||||
* a regular video is minimized (and a new video is not being opened).
|
* a regular video is minimized (and a new video is not being opened).
|
||||||
* @see ShortsPlayerState
|
* @see ShortsPlayerState
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.shared
|
package app.revanced.extension.youtube.shared
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger
|
import app.revanced.extension.shared.Logger
|
||||||
import app.revanced.extension.youtube.Event
|
import app.revanced.extension.youtube.shared.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorts player state.
|
* Shorts player state.
|
||||||
|
|||||||
+65
-22
@@ -35,7 +35,9 @@ import app.revanced.extension.shared.Utils;
|
|||||||
import app.revanced.extension.shared.ui.Dim;
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerControlsVisibility;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
import app.revanced.extension.youtube.shared.VideoState;
|
import app.revanced.extension.youtube.shared.VideoState;
|
||||||
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
|
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
|
||||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
||||||
@@ -128,7 +130,7 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Current segments that have been auto skipped.
|
* Current segments that have been auto skipped.
|
||||||
* If field is non null then the range will always contain the current video time.
|
* If field is non-null then the range will always contain the current video time.
|
||||||
* Range is used to prevent auto-skipping after undo.
|
* Range is used to prevent auto-skipping after undo.
|
||||||
* Android Range object has inclusive end time, unlike {@link SponsorSegment}.
|
* Android Range object has inclusive end time, unlike {@link SponsorSegment}.
|
||||||
*/
|
*/
|
||||||
@@ -136,7 +138,7 @@ public class SegmentPlaybackController {
|
|||||||
private static Range<Long> undoAutoSkipRange;
|
private static Range<Long> undoAutoSkipRange;
|
||||||
/**
|
/**
|
||||||
* Range to undo if the toast is tapped.
|
* Range to undo if the toast is tapped.
|
||||||
* Is always null or identical to the last non null value of {@link #undoAutoSkipRange}.
|
* Is always null or identical to the last non-null value of {@link #undoAutoSkipRange}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Range<Long> undoAutoSkipRangeToast;
|
private static Range<Long> undoAutoSkipRangeToast;
|
||||||
@@ -311,7 +313,10 @@ public class SegmentPlaybackController {
|
|||||||
if (videoId == null || !Settings.SB_ENABLED.get()) {
|
if (videoId == null || !Settings.SB_ENABLED.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
// Cannot use PlayerType to check because on some newer targets
|
||||||
|
// the player type can be updated out of order and incorrectly
|
||||||
|
// is "none" when the regular player is open
|
||||||
|
if (ShortsPlayerState.isOpen()) {
|
||||||
Logger.printDebug(() -> "Ignoring Short");
|
Logger.printDebug(() -> "Ignoring Short");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -382,8 +387,8 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
Logger.printDebug(() -> {
|
Logger.printDebug(() -> {
|
||||||
String visibilityMessage = switch (visibility) {
|
String visibilityMessage = switch (visibility) {
|
||||||
case View.VISIBLE -> "VISIBLE";
|
case View.VISIBLE -> "VISIBLE";
|
||||||
case View.GONE -> "GONE";
|
case View.GONE -> "GONE";
|
||||||
case View.INVISIBLE -> "INVISIBLE";
|
case View.INVISIBLE -> "INVISIBLE";
|
||||||
default -> "UNKNOWN";
|
default -> "UNKNOWN";
|
||||||
};
|
};
|
||||||
@@ -394,12 +399,18 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* When a video ad is playing in a regular video player, segments or the Skip button should be hidden.
|
* When a video ad is playing in a regular video player, segments or the Skip button should be hidden.
|
||||||
|
*
|
||||||
* @return Whether the Ad Progress TextView is visible in the regular video player.
|
* @return Whether the Ad Progress TextView is visible in the regular video player.
|
||||||
*/
|
*/
|
||||||
public static boolean isAdProgressTextVisible() {
|
public static boolean isAdProgressTextVisible() {
|
||||||
return adProgressTextVisibility == View.VISIBLE;
|
return adProgressTextVisibility == View.VISIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private static boolean autoSkipIsEnabledAndPlayerOverlayIsActive() {
|
||||||
|
return Settings.SB_AUTO_HIDE_SKIP_BUTTON.get() &&
|
||||||
|
PlayerControlsVisibility.getCurrent() != PlayerControlsVisibility.PLAYER_CONTROLS_VISIBILITY_HIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
@@ -422,7 +433,7 @@ public class SegmentPlaybackController {
|
|||||||
// Amount of time to look ahead for the next segment,
|
// Amount of time to look ahead for the next segment,
|
||||||
// and the threshold to determine if a scheduled show/hide is at the correct video time when it's run.
|
// and the threshold to determine if a scheduled show/hide is at the correct video time when it's run.
|
||||||
//
|
//
|
||||||
// This value must be greater than largest time between calls to this method (1000ms),
|
// This value must be greater than the largest time between calls to this method (1000ms),
|
||||||
// and must be adjusted for the video speed.
|
// and must be adjusted for the video speed.
|
||||||
//
|
//
|
||||||
// To debug the stale skip logic, set this to a very large value (5000 or more)
|
// To debug the stale skip logic, set this to a very large value (5000 or more)
|
||||||
@@ -490,7 +501,7 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
// Only schedule, if the segment start time is not near the end time of the current segment.
|
// Only schedule, if the segment start time is not near the end time of the current segment.
|
||||||
// This check is needed to prevent scheduled hide and show from clashing with each other.
|
// This check is needed to prevent scheduled hide and show from clashing with each other.
|
||||||
// Instead the upcoming segment will be handled when the current segment scheduled hide calls back into this method.
|
// Instead, the upcoming segment will be handled when the current segment scheduled hide calls back into this method.
|
||||||
final long minTimeBetweenStartEndOfSegments = 1000;
|
final long minTimeBetweenStartEndOfSegments = 1000;
|
||||||
if (foundSegmentCurrentlyPlaying == null
|
if (foundSegmentCurrentlyPlaying == null
|
||||||
|| !foundSegmentCurrentlyPlaying.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) {
|
|| !foundSegmentCurrentlyPlaying.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) {
|
||||||
@@ -519,7 +530,11 @@ public class SegmentPlaybackController {
|
|||||||
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
// Do not hide if auto-hide is enabled and player controls are visible.
|
||||||
|
// Skip button will hide when the overlay controls are dismissed.
|
||||||
|
if (!autoSkipIsEnabledAndPlayerOverlayIsActive()) {
|
||||||
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule a hide, but only if the segment end is near.
|
// Schedule a hide, but only if the segment end is near.
|
||||||
@@ -602,12 +617,12 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}, delayUntilSkip);
|
}, delayUntilSkip);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Clear undo range if video time is outside the segment. Must check last.
|
// Clear undo range if video time is outside the segment. Must check last.
|
||||||
if (undoAutoSkipRange != null && !undoAutoSkipRange.contains(millis)) {
|
if (undoAutoSkipRange != null && !undoAutoSkipRange.contains(millis)) {
|
||||||
Logger.printDebug(() -> "Clearing undo range as current time is now outside range: " + undoAutoSkipRange);
|
Logger.printDebug(() -> "Clearing undo range as current time is now outside range: " + undoAutoSkipRange);
|
||||||
undoAutoSkipRange = null;
|
undoAutoSkipRange = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.printException(() -> "setVideoTime failure", e);
|
Logger.printException(() -> "setVideoTime failure", e);
|
||||||
@@ -615,7 +630,7 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all previously hidden segments that are not longer contained in the given video time.
|
* Removes all previously hidden segments that are no longer contained in the given video time.
|
||||||
*/
|
*/
|
||||||
private static void updateHiddenSegments(long currentVideoTime) {
|
private static void updateHiddenSegments(long currentVideoTime) {
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.removeIf((hiddenSegment) -> {
|
hiddenSkipSegmentsForCurrentVideoTime.removeIf((hiddenSegment) -> {
|
||||||
@@ -629,7 +644,9 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
||||||
if (segment == null) {
|
if (segment == null) {
|
||||||
if (segmentCurrentlyPlaying != null) Logger.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying);
|
if (segmentCurrentlyPlaying != null) {
|
||||||
|
Logger.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying);
|
||||||
|
}
|
||||||
segmentCurrentlyPlaying = null;
|
segmentCurrentlyPlaying = null;
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
@@ -643,7 +660,12 @@ public class SegmentPlaybackController {
|
|||||||
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
||||||
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
||||||
Logger.printDebug(() -> "Ignoring previously auto-hidden segment: " + segment);
|
Logger.printDebug(() -> "Ignoring previously auto-hidden segment: " + segment);
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
// Must set view segment so overlay controls shows the correct skip button.
|
||||||
|
SponsorBlockViewController.setSkipSegment(segment);
|
||||||
|
// Do not hide skip button if
|
||||||
|
if (!autoSkipIsEnabledAndPlayerOverlayIsActive()) {
|
||||||
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
skipSegmentButtonEndTime = System.currentTimeMillis() + getSkipButtonDuration();
|
skipSegmentButtonEndTime = System.currentTimeMillis() + getSkipButtonDuration();
|
||||||
@@ -746,6 +768,15 @@ public class SegmentPlaybackController {
|
|||||||
|| !undoAutoSkipRange.contains(currentVideoTime));
|
|| !undoAutoSkipRange.contains(currentVideoTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean currentlyInsideSkippableSegment() {
|
||||||
|
return segmentCurrentlyPlaying != null || !hiddenSkipSegmentsForCurrentVideoTime.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldNotFadeOutPlayerOverlaySkipButton() {
|
||||||
|
// Only fade out overlay if auto hide is enabled and a scheduled button auto hide is not scheduled.
|
||||||
|
return skipSegmentButtonEndTime != 0 || !Settings.SB_AUTO_HIDE_SKIP_BUTTON.get();
|
||||||
|
}
|
||||||
|
|
||||||
private static void showSkippedSegmentToast(SponsorSegment segment) {
|
private static void showSkippedSegmentToast(SponsorSegment segment) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
toastSegmentSkipped = segment;
|
toastSegmentSkipped = segment;
|
||||||
@@ -757,8 +788,8 @@ public class SegmentPlaybackController {
|
|||||||
final long delayToToastMilliseconds = 250;
|
final long delayToToastMilliseconds = 250;
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
try {
|
try {
|
||||||
// Do not show a toast if the user is scrubbing thru a paused video.
|
// Do not show a toast if the user is scrubbing through a paused video.
|
||||||
// Cannot do this video state check in setTime or before calling this this method,
|
// Cannot do this video state check in setTime or before calling this method,
|
||||||
// as the video state may not be up to date. So instead, only ignore the toast
|
// as the video state may not be up to date. So instead, only ignore the toast
|
||||||
// just before it's about to show since the video state is up to date.
|
// just before it's about to show since the video state is up to date.
|
||||||
if (VideoState.getCurrent() == VideoState.PAUSED) {
|
if (VideoState.getCurrent() == VideoState.PAUSED) {
|
||||||
@@ -792,6 +823,13 @@ public class SegmentPlaybackController {
|
|||||||
Objects.requireNonNull(messageToToast);
|
Objects.requireNonNull(messageToToast);
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
|
if (PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL) {
|
||||||
|
// Cannot easily show a toast since there is no layout view context.
|
||||||
|
// Probably better to not show a toast here anyway.
|
||||||
|
Logger.printDebug(() -> "Not showing undo toast for feed playback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Context currentContext = SponsorBlockViewController.getOverLaysViewGroupContext();
|
Context currentContext = SponsorBlockViewController.getOverLaysViewGroupContext();
|
||||||
if (currentContext == null) {
|
if (currentContext == null) {
|
||||||
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
||||||
@@ -836,13 +874,17 @@ public class SegmentPlaybackController {
|
|||||||
fadeIn.setDuration(fadeDurationFast);
|
fadeIn.setDuration(fadeDurationFast);
|
||||||
fadeOut.setDuration(fadeDurationFast);
|
fadeOut.setDuration(fadeDurationFast);
|
||||||
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
||||||
public void onAnimationStart(Animation animation) { }
|
public void onAnimationStart(Animation animation) {
|
||||||
|
}
|
||||||
|
|
||||||
public void onAnimationEnd(Animation animation) {
|
public void onAnimationEnd(Animation animation) {
|
||||||
if (dialog.isShowing()) {
|
if (dialog.isShowing()) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onAnimationRepeat(Animation animation) { }
|
|
||||||
|
public void onAnimationRepeat(Animation animation) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mainLayout.setOnClickListener(v -> {
|
mainLayout.setOnClickListener(v -> {
|
||||||
@@ -891,7 +933,8 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
public static void onSkipSegmentClicked(SponsorSegment segment) {
|
public static void onSkipSegmentClicked(SponsorSegment segment) {
|
||||||
try {
|
try {
|
||||||
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
|
if (segment != highlightSegment && segment != segmentCurrentlyPlaying
|
||||||
|
&& !hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
||||||
Logger.printException(() -> "error: segment not available to skip"); // Should never happen.
|
Logger.printException(() -> "error: segment not available to skip"); // Should never happen.
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
@@ -994,7 +1037,7 @@ public class SegmentPlaybackController {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void drawSegmentTimeBars(final Canvas canvas, final float posY) {
|
public static void drawSegmentTimeBars(final Canvas canvas, final float posY) {
|
||||||
try {
|
try {
|
||||||
if (segments == null) return;
|
if (segments == null || isAdProgressTextVisible()) return;
|
||||||
final long videoLength = VideoInformation.getVideoLength();
|
final long videoLength = VideoInformation.getVideoLength();
|
||||||
if (videoLength <= 0) return;
|
if (videoLength <= 0) return;
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -29,7 +29,7 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGrou
|
|||||||
@SuppressWarnings("NewApi")
|
@SuppressWarnings("NewApi")
|
||||||
public class SponsorBlockSettings {
|
public class SponsorBlockSettings {
|
||||||
/**
|
/**
|
||||||
* Minimum length a SB user id must be, as set by SB API.
|
* Minimum length an SB user ID must be, as set by SB API.
|
||||||
*/
|
*/
|
||||||
private static final int SB_PRIVATE_USER_ID_MINIMUM_LENGTH = 30;
|
private static final int SB_PRIVATE_USER_ID_MINIMUM_LENGTH = 30;
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ public class SponsorBlockSettings {
|
|||||||
Utils.showToastLong(categoryKey + " unknown behavior key: " + categoryKey);
|
Utils.showToastLong(categoryKey + " unknown behavior key: " + categoryKey);
|
||||||
} else if (category == SegmentCategory.HIGHLIGHT && behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE) {
|
} else if (category == SegmentCategory.HIGHLIGHT && behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE) {
|
||||||
Utils.showToastLong("Skip-once behavior not allowed for " + category.keyValue);
|
Utils.showToastLong("Skip-once behavior not allowed for " + category.keyValue);
|
||||||
category.setBehaviour(CategoryBehaviour.SKIP_AUTOMATICALLY); // Use closest match.
|
category.setBehaviour(CategoryBehaviour.SKIP_AUTOMATICALLY); // Use the closest match.
|
||||||
} else {
|
} else {
|
||||||
category.setBehaviour(behaviour);
|
category.setBehaviour(behaviour);
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ public class SponsorBlockSettings {
|
|||||||
if (settingsJson.has("userID")) {
|
if (settingsJson.has("userID")) {
|
||||||
// User id does not exist if user never voted or created any segments.
|
// User id does not exist if user never voted or created any segments.
|
||||||
String userID = settingsJson.getString("userID");
|
String userID = settingsJson.getString("userID");
|
||||||
if (isValidSBUserId(userID)) {
|
if (isValidSBUserID(userID)) {
|
||||||
Settings.SB_PRIVATE_USER_ID.save(userID);
|
Settings.SB_PRIVATE_USER_ID.save(userID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ public class SponsorBlockSettings {
|
|||||||
categorySelectionsArray.put(behaviorObject);
|
categorySelectionsArray.put(behaviorObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (SponsorBlockSettings.userHasSBPrivateId()) {
|
if (SponsorBlockSettings.userHasSBPrivateID()) {
|
||||||
json.put("userID", Settings.SB_PRIVATE_USER_ID.get());
|
json.put("userID", Settings.SB_PRIVATE_USER_ID.get());
|
||||||
}
|
}
|
||||||
json.put("isVip", Settings.SB_USER_IS_VIP.get());
|
json.put("isVip", Settings.SB_USER_IS_VIP.get());
|
||||||
@@ -183,14 +183,14 @@ public class SponsorBlockSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the categories using flatten json (no embedded dictionaries or arrays).
|
* Export the categories using flatten JSON (no embedded dictionaries or arrays).
|
||||||
*/
|
*/
|
||||||
private static void showExportWarningIfNeeded(@Nullable Context dialogContext) {
|
private static void showExportWarningIfNeeded(@Nullable Context dialogContext) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
initialize();
|
initialize();
|
||||||
|
|
||||||
// If user has a SponsorBlock user id then show a warning.
|
// If user has a SponsorBlock user ID then show a warning.
|
||||||
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
|
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateID()
|
||||||
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
||||||
// Create the custom dialog.
|
// Create the custom dialog.
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
@@ -214,12 +214,12 @@ public class SponsorBlockSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidSBUserId(@NonNull String userId) {
|
public static boolean isValidSBUserID(@NonNull String userID) {
|
||||||
return !userId.isEmpty() && userId.length() >= SB_PRIVATE_USER_ID_MINIMUM_LENGTH;
|
return !userID.isEmpty() && userID.length() >= SB_PRIVATE_USER_ID_MINIMUM_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A non comprehensive check if a SB api server address is valid.
|
* A non-comprehensive check if an SB API server address is valid.
|
||||||
*/
|
*/
|
||||||
public static boolean isValidSBServerAddress(@NonNull String serverAddress) {
|
public static boolean isValidSBServerAddress(@NonNull String serverAddress) {
|
||||||
if (!Patterns.WEB_URL.matcher(serverAddress).matches()) {
|
if (!Patterns.WEB_URL.matcher(serverAddress).matches()) {
|
||||||
@@ -237,12 +237,12 @@ public class SponsorBlockSettings {
|
|||||||
/**
|
/**
|
||||||
* @return if the user has ever voted, created a segment, or imported existing SB settings.
|
* @return if the user has ever voted, created a segment, or imported existing SB settings.
|
||||||
*/
|
*/
|
||||||
public static boolean userHasSBPrivateId() {
|
public static boolean userHasSBPrivateID() {
|
||||||
return !Settings.SB_PRIVATE_USER_ID.get().isEmpty();
|
return !Settings.SB_PRIVATE_USER_ID.get().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this only if a user id is required (creating segments, voting).
|
* Use this only if a user ID is required (creating segments, voting).
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getSBPrivateUserID() {
|
public static String getSBPrivateUserID() {
|
||||||
|
|||||||
+3
-3
@@ -416,10 +416,10 @@ public class SponsorBlockUtils {
|
|||||||
if (!matcher.matches()) {
|
if (!matcher.matches()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
String hoursStr = matcher.group(2); // Hours is optional.
|
String hoursStr = matcher.group(2); // Hours are optional.
|
||||||
String minutesStr = matcher.group(3);
|
String minutesStr = matcher.group(3);
|
||||||
String secondsStr = matcher.group(4);
|
String secondsStr = matcher.group(4);
|
||||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
String millisecondsStr = matcher.group(6); // Milliseconds are optional.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||||
@@ -447,7 +447,7 @@ public class SponsorBlockUtils {
|
|||||||
// Use same time formatting as shown in the video player.
|
// Use same time formatting as shown in the video player.
|
||||||
final long videoLength = VideoInformation.getVideoLength();
|
final long videoLength = VideoInformation.getVideoLength();
|
||||||
|
|
||||||
// Cannot use DateFormatter, as videos over 24 hours will rollover and not display correctly.
|
// Cannot use DateFormatter, as videos over 24 hours will roll over and not display correctly.
|
||||||
final long hours = TimeUnit.MILLISECONDS.toHours(segmentTime);
|
final long hours = TimeUnit.MILLISECONDS.toHours(segmentTime);
|
||||||
final long minutes = TimeUnit.MILLISECONDS.toMinutes(segmentTime) % 60;
|
final long minutes = TimeUnit.MILLISECONDS.toMinutes(segmentTime) % 60;
|
||||||
final long seconds = TimeUnit.MILLISECONDS.toSeconds(segmentTime) % 60;
|
final long seconds = TimeUnit.MILLISECONDS.toSeconds(segmentTime) % 60;
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ public enum CategoryBehaviour {
|
|||||||
SKIP_AUTOMATICALLY_ONCE("skip-once", 3, true, sf("revanced_sb_skip_automatically_once")),
|
SKIP_AUTOMATICALLY_ONCE("skip-once", 3, true, sf("revanced_sb_skip_automatically_once")),
|
||||||
MANUAL_SKIP("manual-skip", 1, false, sf("revanced_sb_skip_showbutton")),
|
MANUAL_SKIP("manual-skip", 1, false, sf("revanced_sb_skip_showbutton")),
|
||||||
SHOW_IN_SEEKBAR("seekbar-only", 0, false, sf("revanced_sb_skip_seekbaronly")),
|
SHOW_IN_SEEKBAR("seekbar-only", 0, false, sf("revanced_sb_skip_seekbaronly")),
|
||||||
// ignored categories are not exported to json, and ignore is the default behavior when importing
|
// ignored categories are not exported to JSON, and ignore is the default behavior when importing
|
||||||
IGNORE("ignore", -1, false, sf("revanced_sb_skip_ignore"));
|
IGNORE("ignore", -1, false, sf("revanced_sb_skip_ignore"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user