mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-08 11:05:49 +01:00
chore: Merge branch dev
to main
(#577)
This commit is contained in:
commit
7760a39602
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_standard_no-wildcard-imports = disabled
|
109
CHANGELOG.md
109
CHANGELOG.md
@ -1,3 +1,112 @@
|
|||||||
|
# [1.5.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.9...v1.5.0-dev.10) (2024-03-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Selectively hide Shorts for home / subscription / search ([#592](https://github.com/ReVanced/revanced-integrations/issues/592)) ([1ee99aa](https://github.com/ReVanced/revanced-integrations/commit/1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a))
|
||||||
|
|
||||||
|
# [1.5.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2024-03-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Check index of pattern in string instead of the other way around ([96a1e46](https://github.com/ReVanced/revanced-integrations/commit/96a1e4680d23be7154bb83290b1887fcd1a22f53))
|
||||||
|
* **YouTube - Hide layout components:** Correctly hide Join button ([b945e2f](https://github.com/ReVanced/revanced-integrations/commit/b945e2f44b1a62326e6d45345c1467668d803f53))
|
||||||
|
* **YouTube - Hide Shorts components:** Correctly hide join button ([b7a8995](https://github.com/ReVanced/revanced-integrations/commit/b7a8995f798e386ee1d9ab5bbd857c1736cc5a29))
|
||||||
|
* **YouTube:** Fix video playback by switching to ReVanced GmsCore vendor ([#589](https://github.com/ReVanced/revanced-integrations/issues/589)) ([6e947e2](https://github.com/ReVanced/revanced-integrations/commit/6e947e24c2ac36e1bddcd25412870a36aa6c85c8))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Filter home/search results by keywords ([#584](https://github.com/ReVanced/revanced-integrations/issues/584)) ([0cbad98](https://github.com/ReVanced/revanced-integrations/commit/0cbad9820577c476f1f29b6ac77611b38afbb950))
|
||||||
|
* **YouTube - Hide Shorts components:** Hide like and dislike buttons ([2a08e5a](https://github.com/ReVanced/revanced-integrations/commit/2a08e5a98e9e9a00bb306313ff487d67c042a92e))
|
||||||
|
* **YouTube - Hide Shorts components:** Hide sound metadata label ([46d8ef6](https://github.com/ReVanced/revanced-integrations/commit/46d8ef6f88bd4c912a45357541291af38b5fc81f))
|
||||||
|
* **YouTube - Hide Shorts components:** Hide title and full video link label ([59165de](https://github.com/ReVanced/revanced-integrations/commit/59165de801a5481fa4055dcf1797fe84dce235c0))
|
||||||
|
|
||||||
|
# [1.5.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2024-03-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Prevent app crash if hiding fullscreen ads is not possible ([#590](https://github.com/ReVanced/revanced-integrations/issues/590)) ([4ec955f](https://github.com/ReVanced/revanced-integrations/commit/4ec955fd0133643826e47be7089fbfa07fd9a089))
|
||||||
|
|
||||||
|
# [1.5.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.6...v1.5.0-dev.7) (2024-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok:** Hook application context earlier to prevent crash ([#588](https://github.com/ReVanced/revanced-integrations/issues/588)) ([9ac2d63](https://github.com/ReVanced/revanced-integrations/commit/9ac2d634897d961eba1b704f2722ea757bb83e0a))
|
||||||
|
|
||||||
|
# [1.5.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.5...v1.5.0-dev.6) (2024-03-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Handle custom preferences ([#586](https://github.com/ReVanced/revanced-integrations/issues/586)) ([ad477e4](https://github.com/ReVanced/revanced-integrations/commit/ad477e4859ef69beda297f7a2a6c29a918077628))
|
||||||
|
|
||||||
|
# [1.5.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.4...v1.5.0-dev.5) (2024-03-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Downloads:** Use external downloader when selecting 'Download' in home feed flyout menu ([#587](https://github.com/ReVanced/revanced-integrations/issues/587)) ([254da31](https://github.com/ReVanced/revanced-integrations/commit/254da31d16c39781f46e1cdea1e9ba22e2fce6d1))
|
||||||
|
|
||||||
|
# [1.5.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.3...v1.5.0-dev.4) (2024-03-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - HDR auto brightness:** Remove non functional and obsolete patch ([#585](https://github.com/ReVanced/revanced-integrations/issues/585)) ([b060732](https://github.com/ReVanced/revanced-integrations/commit/b060732e861b011cac8737ed597385a3315f6057))
|
||||||
|
|
||||||
|
# [1.5.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.2...v1.5.0-dev.3) (2024-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Disable suggested video end screen:** Disable by default to fix autoplay issues ([#578](https://github.com/ReVanced/revanced-integrations/issues/578)) ([b9c1eec](https://github.com/ReVanced/revanced-integrations/commit/b9c1eec69fab64f213dd77d1f932e3244d81ab2d))
|
||||||
|
|
||||||
|
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2024-03-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Downloads:** Use new task context ([#583](https://github.com/ReVanced/revanced-integrations/issues/583)) ([468dfac](https://github.com/ReVanced/revanced-integrations/commit/468dfac0544e282658675a8be65b4e43aa351068))
|
||||||
|
|
||||||
|
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.4...v1.5.0-dev.1) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - External downloader:** Add ability to use in-app download button ([771a0de](https://github.com/ReVanced/revanced-integrations/commit/771a0de3bc9bec3ec5a8e4f8b02edfa9df7b1997))
|
||||||
|
|
||||||
|
## [1.4.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.3...v1.4.1-dev.4) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide seekbar:** Use original seekbar color if Theme patch is not included ([#580](https://github.com/ReVanced/revanced-integrations/issues/580)) ([8d48a90](https://github.com/ReVanced/revanced-integrations/commit/8d48a90a8b8bc7ce9e22580b7a66c4c12fd6d54f))
|
||||||
|
|
||||||
|
## [1.4.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.2...v1.4.1-dev.3) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Revert AGP dependency update ([ab07a56](https://github.com/ReVanced/revanced-integrations/commit/ab07a563b9ef890dc8a673eeb4268ce1c9f15a69))
|
||||||
|
|
||||||
|
## [1.4.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.1...v1.4.1-dev.2) (2024-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Client spoof:** Allow playback for links with timestamp ([#582](https://github.com/ReVanced/revanced-integrations/issues/582)) ([eee3f35](https://github.com/ReVanced/revanced-integrations/commit/eee3f352c59141f47f6bda6c6cd350f1a16f1450))
|
||||||
|
|
||||||
|
## [1.4.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.0...v1.4.1-dev.1) (2024-03-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Announcements:** Only compare ID to not show same announcement after a fix-up ([#579](https://github.com/ReVanced/revanced-integrations/issues/579)) ([5d14f53](https://github.com/ReVanced/revanced-integrations/commit/5d14f53acd0b1eabd6951543edd7d7c662b6c502))
|
||||||
|
|
||||||
# [1.4.0](https://github.com/ReVanced/revanced-integrations/compare/v1.3.2...v1.4.0) (2024-03-02)
|
# [1.4.0](https://github.com/ReVanced/revanced-integrations/compare/v1.3.2...v1.4.0) (2024-03-02)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
|
||||||
|
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -8,13 +6,11 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @noinspection unused
|
* @noinspection unused
|
||||||
@ -61,9 +57,8 @@ public class GmsCoreSupport {
|
|||||||
|
|
||||||
private static String getGmsCoreDownloadLink() {
|
private static String getGmsCoreDownloadLink() {
|
||||||
final var vendor = getGmsCoreVendor();
|
final var vendor = getGmsCoreVendor();
|
||||||
|
//noinspection SwitchStatementWithTooFewBranches
|
||||||
switch (vendor) {
|
switch (vendor) {
|
||||||
case "com.mgoogle":
|
|
||||||
return "https://github.com/TeamVanced/VancedMicroG/releases/latest";
|
|
||||||
case "app.revanced":
|
case "app.revanced":
|
||||||
return "https://github.com/revanced/gmscore/releases/latest";
|
return "https://github.com/revanced/gmscore/releases/latest";
|
||||||
default:
|
default:
|
@ -5,14 +5,15 @@ import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_STACK
|
|||||||
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
|
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +25,7 @@ public class Logger {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return For outer classes, this returns {@link Class#getSimpleName()}.
|
* @return For outer classes, this returns {@link Class#getSimpleName()}.
|
||||||
* For inner, static, or anonymous classes, this returns the simple name of the enclosing class.<br>
|
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
|
||||||
* <br>
|
* <br>
|
||||||
* For example, each of these classes return 'SomethingView':
|
* For example, each of these classes return 'SomethingView':
|
||||||
* <code>
|
* <code>
|
||||||
@ -38,13 +39,13 @@ public class Logger {
|
|||||||
|
|
||||||
String fullClassName = selfClass.getName();
|
String fullClassName = selfClass.getName();
|
||||||
final int dollarSignIndex = fullClassName.indexOf('$');
|
final int dollarSignIndex = fullClassName.indexOf('$');
|
||||||
if (dollarSignIndex == -1) {
|
if (dollarSignIndex < 0) {
|
||||||
return selfClass.getSimpleName(); // already an outer class
|
return selfClass.getSimpleName(); // Already an outer class.
|
||||||
}
|
}
|
||||||
|
|
||||||
// class is inner, static, or anonymous
|
// Class is inner, static, or anonymous.
|
||||||
// parse the simple name full name
|
// Parse the simple name full name.
|
||||||
// a class with no package returns index of -1, but incrementing gives index zero which is correct
|
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
|
||||||
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
|
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
|
||||||
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
|
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
|
||||||
}
|
}
|
||||||
@ -137,11 +138,19 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#context} may not be initialized.
|
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
||||||
* Always logs even if Debugging is not enabled.
|
|
||||||
* Normally this method should not be used.
|
* Normally this method should not be used.
|
||||||
*/
|
*/
|
||||||
public static void initializationError(@NonNull Class<?> callingClass, @NonNull String message, @Nullable Exception ex) {
|
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) {
|
||||||
|
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
||||||
|
* Normally this method should not be used.
|
||||||
|
*/
|
||||||
|
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message,
|
||||||
|
@Nullable Exception ex) {
|
||||||
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
|
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ import kotlin.text.Regex;
|
|||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public static Context context;
|
private static Context context;
|
||||||
|
|
||||||
private static String versionName;
|
private static String versionName;
|
||||||
|
|
||||||
@ -54,13 +54,14 @@ public class Utils {
|
|||||||
try {
|
try {
|
||||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||||
|
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||||
packageInfo = context.getPackageManager().getPackageInfo(
|
packageInfo = packageManager.getPackageInfo(
|
||||||
packageName,
|
packageName,
|
||||||
PackageManager.PackageInfoFlags.of(0)
|
PackageManager.PackageInfoFlags.of(0)
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
packageInfo = context.getPackageManager().getPackageInfo(
|
packageInfo = packageManager.getPackageInfo(
|
||||||
packageName,
|
packageName,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
@ -195,18 +196,29 @@ public class Utils {
|
|||||||
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface MatchFilter<T> {
|
||||||
|
boolean matches(T object);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param searchRecursively If children ViewGroups should also be
|
||||||
|
* recursively searched using depth first search.
|
||||||
* @return The first child view that matches the filter.
|
* @return The first child view that matches the filter.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
|
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, boolean searchRecursively,
|
||||||
|
@NonNull MatchFilter<View> filter) {
|
||||||
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
||||||
View childAt = viewGroup.getChildAt(i);
|
View childAt = viewGroup.getChildAt(i);
|
||||||
//noinspection unchecked
|
|
||||||
if (filter.matches(childAt)) {
|
if (filter.matches(childAt)) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) childAt;
|
return (T) childAt;
|
||||||
}
|
}
|
||||||
|
// Must do recursive after filter check, in case the filter is looking for a ViewGroup.
|
||||||
|
if (searchRecursively && childAt instanceof ViewGroup) {
|
||||||
|
T match = getChildView((ViewGroup) childAt, true, filter);
|
||||||
|
if (match != null) return match;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -222,17 +234,27 @@ public class Utils {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MatchFilter<T> {
|
|
||||||
boolean matches(T object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
Logger.initializationError(Utils.class, "Context is null, returning null!", null);
|
Logger.initializationException(Utils.class, "Context is null, returning null!", null);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setContext(Context appContext) {
|
||||||
|
context = appContext;
|
||||||
|
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
|
||||||
|
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
|
||||||
|
// even though the context is already set before the call.
|
||||||
|
//
|
||||||
|
// The initialization logger methods do not directly or indirectly
|
||||||
|
// reference the Context or any Settings and are unaffected by this problem.
|
||||||
|
//
|
||||||
|
// Info level also helps debug if a patch hook is called before
|
||||||
|
// the context is set since debug logging is off by default.
|
||||||
|
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setClipboard(@NonNull String text) {
|
public static void setClipboard(@NonNull String text) {
|
||||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
|
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
|
||||||
@ -275,7 +297,7 @@ public class Utils {
|
|||||||
Objects.requireNonNull(messageToToast);
|
Objects.requireNonNull(messageToToast);
|
||||||
runOnMainThreadNowOrLater(() -> {
|
runOnMainThreadNowOrLater(() -> {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
Logger.initializationError(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
|
Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
|
||||||
} else {
|
} else {
|
||||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||||
Toast.makeText(context, messageToToast, toastDuration).show();
|
Toast.makeText(context, messageToToast, toastDuration).show();
|
||||||
|
@ -152,47 +152,60 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a UI Preference with the {@link Setting} that backs it.
|
* Handles syncing a UI Preference with the {@link Setting} that backs it.
|
||||||
* If needed, subclasses can override this to handle additional UI Preference types.
|
* If needed, subclasses can override this to handle additional UI Preference types.
|
||||||
*
|
*
|
||||||
|
* @param applySettingToPreference If true, then apply {@link Setting} -> Preference.
|
||||||
|
* If false, then apply {@link Setting} <- Preference.
|
||||||
|
*/
|
||||||
|
protected void syncSettingWithPreference(@NonNull Preference pref,
|
||||||
|
@NonNull Setting<?> setting,
|
||||||
|
boolean applySettingToPreference) {
|
||||||
|
if (pref instanceof SwitchPreference) {
|
||||||
|
SwitchPreference switchPref = (SwitchPreference) pref;
|
||||||
|
BooleanSetting boolSetting = (BooleanSetting) setting;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
switchPref.setChecked(boolSetting.get());
|
||||||
|
} else {
|
||||||
|
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
||||||
|
}
|
||||||
|
} else if (pref instanceof EditTextPreference) {
|
||||||
|
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
editPreference.setText(setting.get().toString());
|
||||||
|
} else {
|
||||||
|
Setting.privateSetValueFromString(setting, editPreference.getText());
|
||||||
|
}
|
||||||
|
} else if (pref instanceof ListPreference) {
|
||||||
|
ListPreference listPref = (ListPreference) pref;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
listPref.setValue(setting.get().toString());
|
||||||
|
} else {
|
||||||
|
Setting.privateSetValueFromString(setting, listPref.getValue());
|
||||||
|
}
|
||||||
|
updateListPreferenceSummary(listPref, setting);
|
||||||
|
} else {
|
||||||
|
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a UI Preference with the {@link Setting} that backs it.
|
||||||
|
*
|
||||||
* @param syncSetting If the UI should be synced {@link Setting} <-> Preference
|
* @param syncSetting If the UI should be synced {@link Setting} <-> Preference
|
||||||
* @param applySettingToPreference If true, then apply {@link Setting} -> Preference.
|
* @param applySettingToPreference If true, then apply {@link Setting} -> Preference.
|
||||||
* If false, then apply {@link Setting} <- Preference.
|
* If false, then apply {@link Setting} <- Preference.
|
||||||
*/
|
*/
|
||||||
protected void updatePreference(@NonNull Preference pref, @NonNull Setting<?> setting,
|
private void updatePreference(@NonNull Preference pref, @NonNull Setting<?> setting,
|
||||||
boolean syncSetting, boolean applySettingToPreference) {
|
boolean syncSetting, boolean applySettingToPreference) {
|
||||||
if (!syncSetting && applySettingToPreference) {
|
if (!syncSetting && applySettingToPreference) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncSetting) {
|
if (syncSetting) {
|
||||||
if (pref instanceof SwitchPreference) {
|
syncSettingWithPreference(pref, setting, applySettingToPreference);
|
||||||
SwitchPreference switchPref = (SwitchPreference) pref;
|
|
||||||
BooleanSetting boolSetting = (BooleanSetting) setting;
|
|
||||||
if (applySettingToPreference) {
|
|
||||||
switchPref.setChecked(boolSetting.get());
|
|
||||||
} else {
|
|
||||||
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
|
||||||
}
|
|
||||||
} else if (pref instanceof EditTextPreference) {
|
|
||||||
EditTextPreference editPreference = (EditTextPreference) pref;
|
|
||||||
if (applySettingToPreference) {
|
|
||||||
editPreference.setText(setting.get().toString());
|
|
||||||
} else {
|
|
||||||
Setting.privateSetValueFromString(setting, editPreference.getText());
|
|
||||||
}
|
|
||||||
} else if (pref instanceof ListPreference) {
|
|
||||||
ListPreference listPref = (ListPreference) pref;
|
|
||||||
if (applySettingToPreference) {
|
|
||||||
listPref.setValue(setting.get().toString());
|
|
||||||
} else {
|
|
||||||
Setting.privateSetValueFromString(setting, listPref.getValue());
|
|
||||||
}
|
|
||||||
updateListPreferenceSummary(listPref, setting);
|
|
||||||
} else {
|
|
||||||
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePreferenceAvailability(pref, setting);
|
updatePreferenceAvailability(pref, setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package app.revanced.integrations.tiktok.settings.preference;
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
import app.revanced.integrations.tiktok.settings.preference.categories.DownloadsPreferenceCategory;
|
import app.revanced.integrations.tiktok.settings.preference.categories.DownloadsPreferenceCategory;
|
||||||
import app.revanced.integrations.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
|
import app.revanced.integrations.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
|
||||||
import app.revanced.integrations.tiktok.settings.preference.categories.IntegrationsPreferenceCategory;
|
import app.revanced.integrations.tiktok.settings.preference.categories.IntegrationsPreferenceCategory;
|
||||||
import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preference fragment for ReVanced settings
|
* Preference fragment for ReVanced settings
|
||||||
@ -13,6 +17,21 @@ import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofP
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void syncSettingWithPreference(@NonNull @NotNull Preference pref,
|
||||||
|
@NonNull @NotNull Setting<?> setting,
|
||||||
|
boolean applySettingToPreference) {
|
||||||
|
if (pref instanceof RangeValuePreference) {
|
||||||
|
RangeValuePreference rangeValuePref = (RangeValuePreference) pref;
|
||||||
|
Setting.privateSetValueFromString(setting, rangeValuePref.getValue());
|
||||||
|
} else if (pref instanceof DownloadPathPreference) {
|
||||||
|
DownloadPathPreference downloadPathPref = (DownloadPathPreference) pref;
|
||||||
|
Setting.privateSetValueFromString(setting, downloadPathPref.getValue());
|
||||||
|
} else {
|
||||||
|
super.syncSettingWithPreference(pref, setting, applySettingToPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
final var context = getContext();
|
final var context = getContext();
|
||||||
|
@ -1,32 +1,37 @@
|
|||||||
package app.revanced.integrations.tiktok.spoof.sim;
|
package app.revanced.integrations.tiktok.spoof.sim;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.tiktok.settings.Settings;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofSimPatch {
|
public class SpoofSimPatch {
|
||||||
public static boolean isEnable() {
|
|
||||||
return Settings.SIM_SPOOF.get();
|
|
||||||
}
|
|
||||||
public static String getCountryIso(String value) {
|
|
||||||
if (isEnable()) {
|
|
||||||
return Settings.SIM_SPOOF_ISO.get();
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private static final Boolean ENABLED = Settings.SIM_SPOOF.get();
|
||||||
|
|
||||||
|
public static String getCountryIso(String value) {
|
||||||
|
if (ENABLED) {
|
||||||
|
String iso = Settings.SIM_SPOOF_ISO.get();
|
||||||
|
Logger.printDebug(() -> "Spoofing sim ISO from: " + value + " to: " + iso);
|
||||||
|
return iso;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getOperator(String value) {
|
public static String getOperator(String value) {
|
||||||
if (isEnable()) {
|
if (ENABLED) {
|
||||||
return Settings.SIMSPOOF_MCCMNC.get();
|
String mcc_mnc = Settings.SIMSPOOF_MCCMNC.get();
|
||||||
} else {
|
Logger.printDebug(() -> "Spoofing sim MCC-MNC from: " + value + " to: " + mcc_mnc);
|
||||||
return value;
|
return mcc_mnc;
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getOperatorName(String value) {
|
public static String getOperatorName(String value) {
|
||||||
if (isEnable()) {
|
if (ENABLED) {
|
||||||
return Settings.SIMSPOOF_OP_NAME.get();
|
String operator = Settings.SIMSPOOF_OP_NAME.get();
|
||||||
} else {
|
Logger.printDebug(() -> "Spoofing sim operator from: " + value + " to: " + operator);
|
||||||
return value;
|
return operator;
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
|||||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
object AdsHook : BaseJsonHook() {
|
object AdsHook : BaseJsonHook() {
|
||||||
/**
|
/**
|
||||||
* Strips JSONObject from promoted ads.
|
* Strips JSONObject from promoted ads.
|
||||||
|
@ -4,7 +4,6 @@ import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
|||||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
object RecommendedUsersHook : BaseJsonHook() {
|
object RecommendedUsersHook : BaseJsonHook() {
|
||||||
/**
|
/**
|
||||||
* Strips JSONObject from recommended users.
|
* Strips JSONObject from recommended users.
|
||||||
|
@ -2,9 +2,12 @@ package app.revanced.integrations.twitter.patches.links;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public final class OpenLinksWithAppChooserPatch {
|
public final class OpenLinksWithAppChooserPatch {
|
||||||
public static void openWithChooser(final Context context, final Intent intent) {
|
public static void openWithChooser(final Context context, final Intent intent) {
|
||||||
|
Log.d("ReVanced", "Opening intent with chooser: " + intent);
|
||||||
|
|
||||||
intent.setAction("android.intent.action.VIEW");
|
intent.setAction("android.intent.action.VIEW");
|
||||||
|
|
||||||
context.startActivity(Intent.createChooser(intent, null));
|
context.startActivity(Intent.createChooser(intent, null));
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package app.revanced.integrations.youtube;
|
package app.revanced.integrations.youtube;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||||
|
|
||||||
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||||
@ -24,18 +28,18 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the pattern is valid to add to this instance.
|
* Helper method for the common usage of converting Strings to raw UTF-8 bytes.
|
||||||
*/
|
*/
|
||||||
public static boolean isValidPattern(byte[] pattern) {
|
public static byte[][] convertStringsToBytes(String... strings) {
|
||||||
for (byte b : pattern) {
|
final int length = strings.length;
|
||||||
if (TrieNode.isInvalidRange((char) b)) {
|
byte[][] replacement = new byte[length][];
|
||||||
return false;
|
for (int i = 0; i < length; i++) {
|
||||||
}
|
replacement[i] = strings[i].getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
return true;
|
return replacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteTrieSearch() {
|
public ByteTrieSearch(@NonNull byte[]... patterns) {
|
||||||
super(new ByteTrieNode());
|
super(new ByteTrieNode(), patterns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,3 @@ class Event<T> {
|
|||||||
observer.invoke(value)
|
observer.invoke(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube;
|
package app.revanced.integrations.youtube;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text pattern searching using a prefix tree (trie).
|
* Text pattern searching using a prefix tree (trie).
|
||||||
*/
|
*/
|
||||||
@ -26,19 +28,7 @@ public final class StringTrieSearch extends TrieSearch<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public StringTrieSearch(@NonNull String... patterns) {
|
||||||
* @return If the pattern is valid to add to this instance.
|
super(new StringTrieNode(), patterns);
|
||||||
*/
|
|
||||||
public static boolean isValidPattern(String pattern) {
|
|
||||||
for (int i = 0, length = pattern.length(); i < length; i++) {
|
|
||||||
if (TrieNode.isInvalidRange(pattern.charAt(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringTrieSearch() {
|
|
||||||
super(new StringTrieNode());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,6 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Searches for a group of different patterns using a trie (prefix tree).
|
* Searches for a group of different patterns using a trie (prefix tree).
|
||||||
* Can significantly speed up searching for multiple patterns.
|
* Can significantly speed up searching for multiple patterns.
|
||||||
*
|
|
||||||
* Currently only supports ASCII non-control characters (letters/numbers/symbols).
|
|
||||||
* But could be modified to also support UTF-8 unicode.
|
|
||||||
*/
|
*/
|
||||||
public abstract class TrieSearch<T> {
|
public abstract class TrieSearch<T> {
|
||||||
|
|
||||||
@ -45,14 +42,14 @@ public abstract class TrieSearch<T> {
|
|||||||
*/
|
*/
|
||||||
private static final class TrieCompressedPath<T> {
|
private static final class TrieCompressedPath<T> {
|
||||||
final T pattern;
|
final T pattern;
|
||||||
final int patternLength;
|
|
||||||
final int patternStartIndex;
|
final int patternStartIndex;
|
||||||
|
final int patternLength;
|
||||||
final TriePatternMatchedCallback<T> callback;
|
final TriePatternMatchedCallback<T> callback;
|
||||||
|
|
||||||
TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback<T> callback) {
|
TrieCompressedPath(T pattern, int patternStartIndex, int patternLength, TriePatternMatchedCallback<T> callback) {
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.patternLength = patternLength;
|
|
||||||
this.patternStartIndex = patternStartIndex;
|
this.patternStartIndex = patternStartIndex;
|
||||||
|
this.patternLength = patternLength;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
|
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
|
||||||
@ -76,19 +73,10 @@ public abstract class TrieSearch<T> {
|
|||||||
*/
|
*/
|
||||||
private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character.
|
private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character.
|
||||||
|
|
||||||
// Support only ASCII letters/numbers/symbols and filter out all control characters.
|
|
||||||
private static final char MIN_VALID_CHAR = 32; // Space character.
|
|
||||||
private static final char MAX_VALID_CHAR = 126; // 127 = delete character.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much to expand the children array when resizing.
|
* How much to expand the children array when resizing.
|
||||||
*/
|
*/
|
||||||
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
||||||
private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
|
||||||
|
|
||||||
static boolean isInvalidRange(char character) {
|
|
||||||
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character this node represents.
|
* Character this node represents.
|
||||||
@ -144,11 +132,11 @@ public abstract class TrieSearch<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pattern Pattern to add.
|
* @param pattern Pattern to add.
|
||||||
* @param patternLength Length of the pattern.
|
|
||||||
* @param patternIndex Current recursive index of the pattern.
|
* @param patternIndex Current recursive index of the pattern.
|
||||||
|
* @param patternLength Length of the pattern.
|
||||||
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
|
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
|
||||||
*/
|
*/
|
||||||
private void addPattern(@NonNull T pattern, int patternLength, int patternIndex,
|
private void addPattern(@NonNull T pattern, int patternIndex, int patternLength,
|
||||||
@Nullable TriePatternMatchedCallback<T> callback) {
|
@Nullable TriePatternMatchedCallback<T> callback) {
|
||||||
if (patternIndex == patternLength) { // Reached the end of the pattern.
|
if (patternIndex == patternLength) { // Reached the end of the pattern.
|
||||||
if (endOfPatternCallback == null) {
|
if (endOfPatternCallback == null) {
|
||||||
@ -165,16 +153,13 @@ public abstract class TrieSearch<T> {
|
|||||||
children = new TrieNode[1];
|
children = new TrieNode[1];
|
||||||
TrieCompressedPath<T> temp = leaf;
|
TrieCompressedPath<T> temp = leaf;
|
||||||
leaf = null;
|
leaf = null;
|
||||||
addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback);
|
addPattern(temp.pattern, temp.patternStartIndex, temp.patternLength, temp.callback);
|
||||||
// Continue onward and add the parameter pattern.
|
// Continue onward and add the parameter pattern.
|
||||||
} else if (children == null) {
|
} else if (children == null) {
|
||||||
leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback);
|
leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final char character = getCharValue(pattern, patternIndex);
|
final char character = getCharValue(pattern, patternIndex);
|
||||||
if (isInvalidRange(character)) {
|
|
||||||
throw new IllegalArgumentException("invalid character at index " + patternIndex + ": " + pattern);
|
|
||||||
}
|
|
||||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||||
TrieNode<T> child = children[arrayIndex];
|
TrieNode<T> child = children[arrayIndex];
|
||||||
if (child == null) {
|
if (child == null) {
|
||||||
@ -185,12 +170,11 @@ public abstract class TrieSearch<T> {
|
|||||||
child = createNode(character);
|
child = createNode(character);
|
||||||
expandChildArray(child);
|
expandChildArray(child);
|
||||||
}
|
}
|
||||||
child.addPattern(pattern, patternLength, patternIndex + 1, callback);
|
child.addPattern(pattern, patternIndex + 1, patternLength, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the children table until all nodes hash to exactly one array index.
|
* Resizes the children table until all nodes hash to exactly one array index.
|
||||||
* Worse case, this will resize the array to {@link #CHILDREN_ARRAY_MAX_SIZE} elements.
|
|
||||||
*/
|
*/
|
||||||
private void expandChildArray(TrieNode<T> child) {
|
private void expandChildArray(TrieNode<T> child) {
|
||||||
int replacementArraySize = Objects.requireNonNull(children).length;
|
int replacementArraySize = Objects.requireNonNull(children).length;
|
||||||
@ -209,7 +193,6 @@ public abstract class TrieSearch<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (collision) {
|
if (collision) {
|
||||||
if (replacementArraySize > CHILDREN_ARRAY_MAX_SIZE) throw new IllegalStateException();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
children = replacement;
|
children = replacement;
|
||||||
@ -232,22 +215,23 @@ public abstract class TrieSearch<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is static and uses a loop to avoid all recursion.
|
* This method is static and uses a loop to avoid all recursion.
|
||||||
* This is done for performance since the JVM does not do tail recursion optimization.
|
* This is done for performance since the JVM does not optimize tail recursion.
|
||||||
*
|
*
|
||||||
* @param startNode Node to start the search from.
|
* @param startNode Node to start the search from.
|
||||||
* @param searchText Text to search for patterns in.
|
* @param searchText Text to search for patterns in.
|
||||||
* @param searchTextLength Length of the search text.
|
* @param searchTextIndex Start index, inclusive.
|
||||||
* @param searchTextIndex Current recursive search text index. Also, the end index of the current pattern match.
|
* @param searchTextEndIndex End index, exclusive.
|
||||||
* @return If any pattern matches, and it's associated callback halted the search.
|
* @return If any pattern matches, and it's associated callback halted the search.
|
||||||
*/
|
*/
|
||||||
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText, final int searchTextLength,
|
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText,
|
||||||
int searchTextIndex, final Object callbackParameter) {
|
int searchTextIndex, final int searchTextEndIndex,
|
||||||
|
final Object callbackParameter) {
|
||||||
TrieNode<T> node = startNode;
|
TrieNode<T> node = startNode;
|
||||||
int currentMatchLength = 0;
|
int currentMatchLength = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
TrieCompressedPath<T> leaf = node.leaf;
|
TrieCompressedPath<T> leaf = node.leaf;
|
||||||
if (leaf != null && leaf.matches(node, searchText, searchTextLength, searchTextIndex, callbackParameter)) {
|
if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) {
|
||||||
return true; // Leaf exists and it matched the search text.
|
return true; // Leaf exists and it matched the search text.
|
||||||
}
|
}
|
||||||
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
|
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
|
||||||
@ -266,7 +250,7 @@ public abstract class TrieSearch<T> {
|
|||||||
if (children == null) {
|
if (children == null) {
|
||||||
return false; // Reached a graph end point and there's no further patterns to search.
|
return false; // Reached a graph end point and there's no further patterns to search.
|
||||||
}
|
}
|
||||||
if (searchTextIndex == searchTextLength) {
|
if (searchTextIndex == searchTextEndIndex) {
|
||||||
return false; // Reached end of the search text and found no matches.
|
return false; // Reached end of the search text and found no matches.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,8 +307,10 @@ public abstract class TrieSearch<T> {
|
|||||||
*/
|
*/
|
||||||
private final List<T> patterns = new ArrayList<>();
|
private final List<T> patterns = new ArrayList<>();
|
||||||
|
|
||||||
TrieSearch(@NonNull TrieNode<T> root) {
|
@SafeVarargs
|
||||||
|
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) {
|
||||||
this.root = Objects.requireNonNull(root);
|
this.root = Objects.requireNonNull(root);
|
||||||
|
addPatterns(patterns);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
@ -355,7 +341,7 @@ public abstract class TrieSearch<T> {
|
|||||||
if (patternLength == 0) return; // Nothing to match
|
if (patternLength == 0) return; // Nothing to match
|
||||||
|
|
||||||
patterns.add(pattern);
|
patterns.add(pattern);
|
||||||
root.addPattern(pattern, patternLength, 0, callback);
|
root.addPattern(pattern, 0, patternLength, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean matches(@NonNull T textToSearch) {
|
public final boolean matches(@NonNull T textToSearch) {
|
||||||
@ -398,7 +384,7 @@ public abstract class TrieSearch<T> {
|
|||||||
return false; // No patterns were added.
|
return false; // No patterns were added.
|
||||||
}
|
}
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
for (int i = startIndex; i < endIndex; i++) {
|
||||||
if (TrieNode.matches(root, textToSearch, endIndex, i, callbackParameter)) return true;
|
if (TrieNode.matches(root, textToSearch, i, endIndex, callbackParameter)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
package app.revanced.integrations.youtube.patches;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.StringRef;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class DownloadsPatch {
|
||||||
|
|
||||||
|
private static WeakReference<Activity> activityRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void activityCreated(Activity mainActivity) {
|
||||||
|
activityRef = new WeakReference<>(mainActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Called from the in app download hook,
|
||||||
|
* for both the player action button (below the video)
|
||||||
|
* and the 'Download video' flyout option for feed videos.
|
||||||
|
*
|
||||||
|
* Appears to always be called from the main thread.
|
||||||
|
*/
|
||||||
|
public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) {
|
||||||
|
try {
|
||||||
|
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If possible, use the main activity as the context.
|
||||||
|
// Otherwise fall back on using the application context.
|
||||||
|
Context context = activityRef.get();
|
||||||
|
boolean isActivityContext = true;
|
||||||
|
if (context == null) {
|
||||||
|
// Utils context is the application context, and not an activity context.
|
||||||
|
context = Utils.getContext();
|
||||||
|
isActivityContext = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchExternalDownloader(videoId, context, isActivityContext);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "inAppDownloadButtonOnClick failure", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
|
||||||
|
* the downloader is opened as a new task (which forces YT to minimize).
|
||||||
|
*/
|
||||||
|
public static void launchExternalDownloader(@NonNull String videoId,
|
||||||
|
@NonNull Context context, boolean isActivityContext) {
|
||||||
|
try {
|
||||||
|
Objects.requireNonNull(videoId);
|
||||||
|
Logger.printDebug(() -> "Launching external downloader with context: " + context);
|
||||||
|
|
||||||
|
// Trim string to avoid any accidental whitespace.
|
||||||
|
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
||||||
|
|
||||||
|
boolean packageEnabled = false;
|
||||||
|
try {
|
||||||
|
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
|
||||||
|
} catch (PackageManager.NameNotFoundException error) {
|
||||||
|
Logger.printDebug(() -> "External downloader could not be found: " + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the package is not installed, show the toast
|
||||||
|
if (!packageEnabled) {
|
||||||
|
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = "https://youtu.be/" + videoId;
|
||||||
|
Intent intent = new Intent("android.intent.action.SEND");
|
||||||
|
intent.setType("text/plain");
|
||||||
|
intent.setPackage(downloaderPackageName);
|
||||||
|
intent.putExtra("android.intent.extra.TEXT", content);
|
||||||
|
if (!isActivityContext) {
|
||||||
|
Logger.printDebug(() -> "Using new task intent");
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
}
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "launchExternalDownloader failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,12 @@ import app.revanced.integrations.youtube.settings.Settings;
|
|||||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity;
|
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch class for 'hdr-auto-brightness' patch
|
* Patch class for 'hdr-auto-brightness' patch.
|
||||||
|
*
|
||||||
|
* Edit: This patch no longer does anything, as YT already uses BRIGHTNESS_OVERRIDE_NONE
|
||||||
|
* as the default brightness level. The hooked code was also removed from YT 19.09+ as well.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class HDRAutoBrightnessPatch {
|
public class HDRAutoBrightnessPatch {
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,7 @@ public class HideBreakingNewsPatch {
|
|||||||
* Breaking news does not appear to be present in these older versions anyways.
|
* Breaking news does not appear to be present in these older versions anyways.
|
||||||
*/
|
*/
|
||||||
private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory =
|
private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory =
|
||||||
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("17.31.00");
|
SpoofAppVersionPatch.isSpoofingToLessThan("18.01.00");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
|
@ -1,40 +1,41 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
package app.revanced.integrations.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class NavigationButtonsPatch {
|
public final class NavigationButtonsPatch {
|
||||||
public static Enum lastNavigationButton;
|
|
||||||
|
|
||||||
public static void hideCreateButton(final View view) {
|
private static final Map<NavigationButton, Boolean> shouldHideMap = new EnumMap<>(NavigationButton.class) {
|
||||||
view.setVisibility(Settings.HIDE_CREATE_BUTTON.get() ? View.GONE : View.VISIBLE);
|
{
|
||||||
}
|
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
||||||
|
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
||||||
|
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||||
|
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean switchCreateWithNotificationButton() {
|
public static boolean switchCreateWithNotificationButton() {
|
||||||
return Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideButton(final View buttonView) {
|
/**
|
||||||
if (lastNavigationButton == null) return;
|
* Injection point.
|
||||||
|
*/
|
||||||
for (NavigationButton button : NavigationButton.values())
|
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||||
if (button.name.equals(lastNavigationButton.name()))
|
if (Boolean.TRUE.equals(shouldHideMap.get(button))) {
|
||||||
if (button.enabled) buttonView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
}
|
|
||||||
|
|
||||||
private enum NavigationButton {
|
|
||||||
HOME("PIVOT_HOME", Settings.HIDE_HOME_BUTTON.get()),
|
|
||||||
SHORTS("TAB_SHORTS", Settings.HIDE_SHORTS_BUTTON.get()),
|
|
||||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
|
||||||
private final boolean enabled;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
NavigationButton(final String name, final boolean enabled) {
|
|
||||||
this.name = name;
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,24 +6,12 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import app.revanced.integrations.youtube.shared.PlayerOverlays;
|
import app.revanced.integrations.youtube.shared.PlayerOverlays;
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook receiver class for 'player-overlays-hook' patch
|
|
||||||
*
|
|
||||||
* @usedBy app.revanced.patches.youtube.misc.playeroverlay.patch.PlayerOverlaysHookPatch
|
|
||||||
* @smali Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch;
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlayerOverlaysHookPatch {
|
public class PlayerOverlaysHookPatch {
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
|
||||||
* @param thisRef reference to the view
|
|
||||||
* @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V
|
|
||||||
*/
|
*/
|
||||||
public static void YouTubePlayerOverlaysLayout_onFinishInflateHook(@Nullable Object thisRef) {
|
public static void playerOverlayInflated(ViewGroup group) {
|
||||||
if (thisRef == null) return;
|
PlayerOverlays.attach(group);
|
||||||
if (thisRef instanceof ViewGroup) {
|
|
||||||
PlayerOverlays.attach((ViewGroup) thisRef);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -46,7 +46,7 @@ import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTu
|
|||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
||||||
SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40");
|
SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RYD data for the current video on screen.
|
* RYD data for the current video on screen.
|
||||||
|
@ -6,12 +6,11 @@ import android.text.Html;
|
|||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
import app.revanced.integrations.youtube.patches.announcements.requests.AnnouncementsRoutes;
|
import app.revanced.integrations.youtube.patches.announcements.requests.AnnouncementsRoutes;
|
||||||
import app.revanced.integrations.youtube.requests.Requester;
|
import app.revanced.integrations.youtube.requests.Requester;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -19,7 +18,6 @@ import java.net.HttpURLConnection;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
@ -27,8 +25,6 @@ import static app.revanced.integrations.youtube.patches.announcements.requests.A
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class AnnouncementsPatch {
|
public final class AnnouncementsPatch {
|
||||||
private final static String CONSUMER = getOrSetConsumer();
|
|
||||||
|
|
||||||
private AnnouncementsPatch() {
|
private AnnouncementsPatch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,16 +38,17 @@ public final class AnnouncementsPatch {
|
|||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(
|
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(
|
||||||
GET_LATEST_ANNOUNCEMENT, CONSUMER, Locale.getDefault().toLanguageTag());
|
GET_LATEST_ANNOUNCEMENT, Locale.getDefault().toLanguageTag());
|
||||||
|
|
||||||
Logger.printDebug(() -> "Get latest announcement route connection url: " + connection.getURL());
|
Logger.printDebug(() -> "Get latest announcement 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.
|
||||||
if (connection.getResponseCode() != 200) {
|
if (connection.getResponseCode() != 200) {
|
||||||
if (Settings.ANNOUNCEMENT_LAST_HASH.get().isEmpty()) return;
|
if (Settings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
|
||||||
|
return;
|
||||||
|
|
||||||
Settings.ANNOUNCEMENT_LAST_HASH.resetToDefault();
|
Settings.ANNOUNCEMENT_LAST_ID.resetToDefault();
|
||||||
Utils.showToastLong(str("revanced_announcements_connection_failed"));
|
Utils.showToastLong(str("revanced_announcements_connection_failed"));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -65,22 +62,20 @@ public final class AnnouncementsPatch {
|
|||||||
|
|
||||||
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
||||||
|
|
||||||
// Do not show the announcement if it is older or the same as the last one.
|
|
||||||
final byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(jsonString.getBytes(StandardCharsets.UTF_8));
|
|
||||||
final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes);
|
|
||||||
if (hash.equals(Settings.ANNOUNCEMENT_LAST_HASH.get())) return;
|
|
||||||
|
|
||||||
// Parse the announcement. Fall-back to raw string if it fails.
|
// Parse the announcement. Fall-back to raw string if it fails.
|
||||||
|
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
String title;
|
String title;
|
||||||
String message;
|
String message;
|
||||||
Level level = Level.INFO;
|
Level level = Level.INFO;
|
||||||
try {
|
try {
|
||||||
final var announcement = new JSONObject(jsonString);
|
final var announcement = new JSONObject(jsonString);
|
||||||
|
|
||||||
|
id = announcement.getInt("id");
|
||||||
title = announcement.getString("title");
|
title = announcement.getString("title");
|
||||||
message = announcement.getJSONObject("content").getString("message");
|
message = announcement.getJSONObject("content").getString("message");
|
||||||
|
|
||||||
if (!announcement.isNull("level")) level = Level.fromInt(announcement.getInt("level"));
|
if (!announcement.isNull("level")) level = Level.fromInt(announcement.getInt("level"));
|
||||||
|
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
|
Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
|
||||||
|
|
||||||
@ -88,6 +83,28 @@ public final class AnnouncementsPatch {
|
|||||||
message = jsonString;
|
message = jsonString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this migration code after a few months.
|
||||||
|
if (!Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.isSetToDefault()){
|
||||||
|
final byte[] hashBytes = MessageDigest
|
||||||
|
.getInstance("SHA-256")
|
||||||
|
.digest(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes);
|
||||||
|
|
||||||
|
// Migrate to saving the id instead of the hash.
|
||||||
|
if (hash.equals(Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.get())) {
|
||||||
|
Settings.ANNOUNCEMENT_LAST_ID.save(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.resetToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||||
|
if (Settings.ANNOUNCEMENT_LAST_ID.get() == id) return;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int finalId = id;
|
||||||
final var finalTitle = title;
|
final var finalTitle = title;
|
||||||
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
||||||
final Level finalLevel = level;
|
final Level finalLevel = level;
|
||||||
@ -99,7 +116,7 @@ public final class AnnouncementsPatch {
|
|||||||
.setMessage(finalMessage)
|
.setMessage(finalMessage)
|
||||||
.setIcon(finalLevel.icon)
|
.setIcon(finalLevel.icon)
|
||||||
.setPositiveButton("Ok", (dialog, which) -> {
|
.setPositiveButton("Ok", (dialog, which) -> {
|
||||||
Settings.ANNOUNCEMENT_LAST_HASH.save(hash);
|
Settings.ANNOUNCEMENT_LAST_ID.save(finalId);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}).setNegativeButton("Dismiss", (dialog, which) -> {
|
}).setNegativeButton("Dismiss", (dialog, which) -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
@ -119,27 +136,6 @@ public final class AnnouncementsPatch {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the last announcement hash if it is not empty.
|
|
||||||
*
|
|
||||||
* @return true if the last announcement hash was empty.
|
|
||||||
*/
|
|
||||||
private static boolean emptyLastAnnouncementHash() {
|
|
||||||
if (Settings.ANNOUNCEMENT_LAST_HASH.get().isEmpty()) return true;
|
|
||||||
Settings.ANNOUNCEMENT_LAST_HASH.resetToDefault();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getOrSetConsumer() {
|
|
||||||
final var consumer = Settings.ANNOUNCEMENT_CONSUMER.get();
|
|
||||||
if (!consumer.isEmpty()) return consumer;
|
|
||||||
|
|
||||||
final var uuid = UUID.randomUUID().toString();
|
|
||||||
Settings.ANNOUNCEMENT_CONSUMER.save(uuid);
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use better icons.
|
// TODO: Use better icons.
|
||||||
private enum Level {
|
private enum Level {
|
||||||
INFO(android.R.drawable.ic_dialog_info),
|
INFO(android.R.drawable.ic_dialog_info),
|
||||||
|
@ -14,7 +14,7 @@ public class AnnouncementsRoutes {
|
|||||||
/**
|
/**
|
||||||
* 'language' parameter is IETF format (for USA it would be 'en-us').
|
* 'language' parameter is IETF format (for USA it would be 'en-us').
|
||||||
*/
|
*/
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?consumer={consumer}&language={language}");
|
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?language={language}");
|
||||||
|
|
||||||
private AnnouncementsRoutes() {
|
private AnnouncementsRoutes() {
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -170,7 +172,24 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
// Must run off main thread (Odd, but whatever).
|
// Must run off main thread (Odd, but whatever).
|
||||||
Utils.runOnBackgroundThread(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK));
|
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);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import java.util.regex.Pattern;
|
|||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.Utils;
|
import app.revanced.integrations.shared.Utils;
|
||||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,10 +29,6 @@ final class CustomFilter extends Filter {
|
|||||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showInvalidCharactersToast(@NonNull String expression) {
|
|
||||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_characters", expression));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CustomFilterGroup extends StringFilterGroup {
|
private static class CustomFilterGroup extends StringFilterGroup {
|
||||||
/**
|
/**
|
||||||
* Optional character for the path that indicates the custom filter path must match the start.
|
* Optional character for the path that indicates the custom filter path must match the start.
|
||||||
@ -73,7 +68,7 @@ final class CustomFilter extends Filter {
|
|||||||
Matcher matcher = pattern.matcher(expression);
|
Matcher matcher = pattern.matcher(expression);
|
||||||
if (!matcher.find()) {
|
if (!matcher.find()) {
|
||||||
showInvalidSyntaxToast(expression);
|
showInvalidSyntaxToast(expression);
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String mapKey = matcher.group(1);
|
final String mapKey = matcher.group(1);
|
||||||
@ -84,13 +79,7 @@ final class CustomFilter extends Filter {
|
|||||||
|
|
||||||
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
||||||
showInvalidSyntaxToast(expression);
|
showInvalidSyntaxToast(expression);
|
||||||
return null;
|
continue;
|
||||||
}
|
|
||||||
if (!StringTrieSearch.isValidPattern(path)
|
|
||||||
|| (hasBufferSymbol && !StringTrieSearch.isValidPattern(bufferString))) {
|
|
||||||
// Currently only ASCII is allowed.
|
|
||||||
showInvalidCharactersToast(path);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use one group object for all expressions with the same path.
|
// Use one group object for all expressions with the same path.
|
||||||
@ -149,11 +138,6 @@ final class CustomFilter extends Filter {
|
|||||||
|
|
||||||
public CustomFilter() {
|
public CustomFilter() {
|
||||||
Collection<CustomFilterGroup> groups = CustomFilterGroup.parseCustomFilterGroups();
|
Collection<CustomFilterGroup> groups = CustomFilterGroup.parseCustomFilterGroups();
|
||||||
if (groups == null) {
|
|
||||||
Settings.CUSTOM_FILTER_STRINGS.resetToDefault();
|
|
||||||
Utils.showToastLong(str("revanced_custom_filter_toast_reset"));
|
|
||||||
groups = Objects.requireNonNull(CustomFilterGroup.parseCustomFilterGroups());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!groups.isEmpty()) {
|
if (!groups.isEmpty()) {
|
||||||
CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]);
|
CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]);
|
||||||
|
@ -0,0 +1,280 @@
|
|||||||
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.youtube.ByteTrieSearch.convertStringsToBytes;
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Allows hiding home feed and search results based on keywords and/or channel names.
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* - Searching for a keyword phrase will give no search results.
|
||||||
|
* This is because the buffer for each video contains the text the user searched for, and everything
|
||||||
|
* will be filtered away (even if that video title/channel does not contain any keywords).
|
||||||
|
* - Filtering a channel name can still show Shorts from that channel in the search results.
|
||||||
|
* The most common Shorts layouts do not include the channel name, so they will not be filtered.
|
||||||
|
* - 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
|
||||||
|
* appear outside the filtered components so they are not caught.
|
||||||
|
* - Keywords are case sensitive, but some casing variation is manually added.
|
||||||
|
* (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
|
||||||
|
* will always be hidden. This patch checks for some words of these words.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
final class KeywordContentFilter extends Filter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum keyword/phrase length to prevent excessively broad content filtering.
|
||||||
|
*/
|
||||||
|
private static final int MINIMUM_KEYWORD_LENGTH = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strings found in the buffer for every videos.
|
||||||
|
* Full strings should be specified, as they are compared using {@link String#contains(CharSequence)}.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private static final String[] STRINGS_IN_EVERY_BUFFER = {
|
||||||
|
// Video playback data.
|
||||||
|
"https://i.ytimg.com/vi/", // Thumbnail url.
|
||||||
|
"sddefault.jpg", // More video sizes exist, but for most devices only these 2 are used.
|
||||||
|
"hqdefault.webp",
|
||||||
|
"googlevideo.com/initplayback?source=youtube", // Video url.
|
||||||
|
"ANDROID", // Video url parameter.
|
||||||
|
// Video decoders.
|
||||||
|
"OMX.ffmpeg.vp9.decoder",
|
||||||
|
"OMX.Intel.sw_vd.vp9",
|
||||||
|
"OMX.sprd.av1.decoder",
|
||||||
|
"OMX.MTK.VIDEO.DECODER.SW.VP9",
|
||||||
|
"c2.android.av1.decoder",
|
||||||
|
"c2.mtk.sw.vp9.decoder",
|
||||||
|
// User analytics.
|
||||||
|
"https://ad.doubleclick.net/ddm/activity/",
|
||||||
|
"DEVICE_ADVERTISER_ID_FOR_CONVERSION_TRACKING",
|
||||||
|
// Litho components frequently found in the buffer that belong to the path filter items.
|
||||||
|
"metadata.eml",
|
||||||
|
"thumbnail.eml",
|
||||||
|
"avatar.eml",
|
||||||
|
"overflow_button.eml",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substrings that are always first in the identifier.
|
||||||
|
*/
|
||||||
|
private final StringFilterGroup startsWithFilter = new StringFilterGroup(
|
||||||
|
null, // Multiple settings are used and must be individually checked if active.
|
||||||
|
"home_video_with_context.eml",
|
||||||
|
"search_video_with_context.eml",
|
||||||
|
"video_with_context.eml", // Subscription tab videos.
|
||||||
|
"related_video_with_context.eml",
|
||||||
|
"compact_video.eml",
|
||||||
|
"inline_shorts",
|
||||||
|
"shorts_video_cell",
|
||||||
|
"shorts_pivot_item.eml"
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substrings that are never at the start of the path.
|
||||||
|
*/
|
||||||
|
private final StringFilterGroup containsFilter = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"modern_type_shelf_header_content.eml",
|
||||||
|
"shorts_lockup_cell.eml" // Part of 'shorts_shelf_carousel.eml'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last value of {@link Settings#HIDE_KEYWORD_CONTENT_PHRASES}
|
||||||
|
* parsed and loaded into {@link #bufferSearch}.
|
||||||
|
* Allows changing the keywords without restarting the app.
|
||||||
|
*/
|
||||||
|
private volatile String lastKeywordPhrasesParsed;
|
||||||
|
|
||||||
|
private volatile ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
|
private static void logNavigationState(String state) {
|
||||||
|
// Enable locally to debug filtering. Default off to reduce log spam.
|
||||||
|
final boolean LOG_NAVIGATION_STATE = false;
|
||||||
|
// noinspection ConstantValue
|
||||||
|
if (LOG_NAVIGATION_STATE) {
|
||||||
|
Logger.printDebug(() -> "Navigation state: " + state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideKeywordSettingIsActive() {
|
||||||
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
|
// Must check first. Search bar can be active with almost any tab.
|
||||||
|
logNavigationState("Search");
|
||||||
|
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
||||||
|
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
|
// For now, consider the under video results the same as the home feed.
|
||||||
|
logNavigationState("Player active");
|
||||||
|
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
|
} else if (NavigationButton.HOME.isSelected()) {
|
||||||
|
logNavigationState("Home tab");
|
||||||
|
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
|
} else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
||||||
|
logNavigationState("Subscription tab");
|
||||||
|
return Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
|
||||||
|
} else {
|
||||||
|
// User is in the Library or Notifications tab.
|
||||||
|
logNavigationState("Ignored tab");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change first letter of the first word to use title case.
|
||||||
|
*/
|
||||||
|
private static String titleCaseFirstWordOnly(String sentence) {
|
||||||
|
if (sentence.isEmpty()) {
|
||||||
|
return sentence;
|
||||||
|
}
|
||||||
|
final int firstCodePoint = sentence.codePointAt(0);
|
||||||
|
// In some non English languages title case is different than upper case.
|
||||||
|
return new StringBuilder()
|
||||||
|
.appendCodePoint(Character.toTitleCase(firstCodePoint))
|
||||||
|
.append(sentence, Character.charCount(firstCodePoint), sentence.length())
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uppercase the first letter of each word.
|
||||||
|
*/
|
||||||
|
private static String capitalizeAllFirstLetters(String sentence) {
|
||||||
|
if (sentence.isEmpty()) {
|
||||||
|
return sentence;
|
||||||
|
}
|
||||||
|
final int delimiter = ' ';
|
||||||
|
// Use code points and not characters to handle unicode surrogates.
|
||||||
|
int[] codePoints = sentence.codePoints().toArray();
|
||||||
|
boolean capitalizeNext = true;
|
||||||
|
for (int i = 0, length = codePoints.length; i < length; i++) {
|
||||||
|
final int codePoint = codePoints[i];
|
||||||
|
if (codePoint == delimiter) {
|
||||||
|
capitalizeNext = true;
|
||||||
|
} else if (capitalizeNext) {
|
||||||
|
codePoints[i] = Character.toUpperCase(codePoint);
|
||||||
|
capitalizeNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(codePoints, 0, codePoints.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the phrase will will hide all videos. Not an exhaustive check.
|
||||||
|
*/
|
||||||
|
private static boolean phrasesWillHideAllVideos(@NonNull String[] phrases) {
|
||||||
|
for (String commonString : STRINGS_IN_EVERY_BUFFER) {
|
||||||
|
if (Utils.containsAny(commonString, phrases)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
|
||||||
|
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
||||||
|
if (rawKeywords == lastKeywordPhrasesParsed) {
|
||||||
|
Logger.printDebug(() -> "Using previously initialized search");
|
||||||
|
return; // Another thread won the race, and search is already initialized.
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteTrieSearch search = new ByteTrieSearch();
|
||||||
|
String[] split = rawKeywords.split("\n");
|
||||||
|
if (split.length != 0) {
|
||||||
|
// Linked Set so log statement are more organized and easier to read.
|
||||||
|
Set<String> keywords = new LinkedHashSet<>(10 * split.length);
|
||||||
|
|
||||||
|
for (String phrase : split) {
|
||||||
|
// Remove any trailing white space the user may have accidentally included.
|
||||||
|
phrase = phrase.stripTrailing();
|
||||||
|
if (phrase.isBlank()) continue;
|
||||||
|
|
||||||
|
if (phrase.length() < MINIMUM_KEYWORD_LENGTH) {
|
||||||
|
// Do not reset the setting. Keep the invalid keywords so the user can fix the mistake.
|
||||||
|
Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_length", phrase, MINIMUM_KEYWORD_LENGTH));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add common casing that might appear.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// But to support Unicode with ByteTrieSearch would require major changes because
|
||||||
|
// UTF-8 characters can be different byte lengths, which does
|
||||||
|
// not allow comparing two different byte arrays using simple plain array indexes.
|
||||||
|
//
|
||||||
|
// Instead add all common case variations of the words.
|
||||||
|
String[] phraseVariations = {
|
||||||
|
phrase,
|
||||||
|
phrase.toLowerCase(),
|
||||||
|
titleCaseFirstWordOnly(phrase),
|
||||||
|
capitalizeAllFirstLetters(phrase),
|
||||||
|
phrase.toUpperCase()
|
||||||
|
};
|
||||||
|
if (phrasesWillHideAllVideos(phraseVariations)) {
|
||||||
|
Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_common", phrase));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
keywords.addAll(Arrays.asList(phraseVariations));
|
||||||
|
}
|
||||||
|
|
||||||
|
search.addPatterns(convertStringsToBytes(keywords.toArray(new String[0])));
|
||||||
|
Logger.printDebug(() -> "Search using: (" + search.getEstimatedMemorySize() + " KB) keywords: " + keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferSearch = search;
|
||||||
|
lastKeywordPhrasesParsed = rawKeywords; // Must set last.
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeywordContentFilter() {
|
||||||
|
// Keywords are parsed on first call to isFiltered()
|
||||||
|
addPathCallbacks(startsWithFilter, containsFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hideKeywordSettingIsActive()) return false;
|
||||||
|
|
||||||
|
// Field is intentionally compared using reference equality.
|
||||||
|
if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
|
||||||
|
// User changed the keywords.
|
||||||
|
parseKeywords();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bufferSearch.matches(protobufBufferArray)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,9 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
private final ByteArrayFilterGroup searchResultRecommendations;
|
private final ByteArrayFilterGroup searchResultRecommendations;
|
||||||
private final StringFilterGroup searchResultVideo;
|
private final StringFilterGroup searchResultVideo;
|
||||||
|
private final StringFilterGroup compactChannelBarInner;
|
||||||
|
private final StringFilterGroup compactChannelBarInnerButton;
|
||||||
|
private final ByteArrayFilterGroup joinMembershipButton;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
mixPlaylistsExceptions.addPatterns(
|
mixPlaylistsExceptions.addPatterns(
|
||||||
@ -37,6 +40,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@ -194,9 +198,19 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"set_reminder_button"
|
"set_reminder_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var joinMembership = new StringFilterGroup(
|
compactChannelBarInner = new StringFilterGroup(
|
||||||
Settings.HIDE_JOIN_MEMBERSHIP_BUTTON,
|
Settings.HIDE_JOIN_MEMBERSHIP_BUTTON,
|
||||||
"compact_sponsor_button"
|
"compact_channel_bar_inner"
|
||||||
|
);
|
||||||
|
|
||||||
|
compactChannelBarInnerButton = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"|button.eml|"
|
||||||
|
);
|
||||||
|
|
||||||
|
joinMembershipButton = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"sponsorships"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var channelWatermark = new StringFilterGroup(
|
final var channelWatermark = new StringFilterGroup(
|
||||||
@ -233,7 +247,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
quickActions,
|
quickActions,
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
compactBanner,
|
compactBanner,
|
||||||
joinMembership,
|
compactChannelBarInner,
|
||||||
medicalPanel,
|
medicalPanel,
|
||||||
videoQualityMenuFooter,
|
videoQualityMenuFooter,
|
||||||
infoPanel,
|
infoPanel,
|
||||||
@ -265,6 +279,18 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
|
if (matchedGroup == compactChannelBarInner) {
|
||||||
|
if (compactChannelBarInnerButton.check(path).isFiltered()) {
|
||||||
|
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
|
||||||
|
// it's safe to assume that the button is the only thing that should be hidden.
|
||||||
|
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) {
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This also hides the feed Shorts shelf header
|
// TODO: This also hides the feed Shorts shelf header
|
||||||
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
@ -15,7 +14,6 @@ import java.util.Spliterator;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||||
@ -124,7 +122,7 @@ class StringFilterGroup extends FilterGroup<String> {
|
|||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
for (String pattern : filters) {
|
for (String pattern : filters) {
|
||||||
if (!string.isEmpty()) {
|
if (!string.isEmpty()) {
|
||||||
final int indexOf = pattern.indexOf(string);
|
final int indexOf = string.indexOf(pattern);
|
||||||
if (indexOf >= 0) {
|
if (indexOf >= 0) {
|
||||||
matchedIndex = indexOf;
|
matchedIndex = indexOf;
|
||||||
matchedLength = pattern.length();
|
matchedLength = pattern.length();
|
||||||
@ -190,9 +188,8 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||||||
/**
|
/**
|
||||||
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
||||||
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void buildFailurePatterns() {
|
private synchronized void buildFailurePatterns() {
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.Utils.hideViewBy1dpUnderCondition;
|
import app.revanced.integrations.shared.Utils;
|
||||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
@ -21,18 +25,24 @@ public final class ShortsFilter extends Filter {
|
|||||||
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
||||||
|
|
||||||
private final StringFilterGroup channelBar;
|
private final StringFilterGroup channelBar;
|
||||||
|
private final StringFilterGroup fullVideoLinkLabel;
|
||||||
|
private final StringFilterGroup videoTitle;
|
||||||
|
private final StringFilterGroup reelSoundMetadata;
|
||||||
private final StringFilterGroup subscribeButton;
|
private final StringFilterGroup subscribeButton;
|
||||||
private final StringFilterGroup subscribeButtonPaused;
|
private final StringFilterGroup subscribeButtonPaused;
|
||||||
private final StringFilterGroup soundButton;
|
private final StringFilterGroup soundButton;
|
||||||
private final StringFilterGroup infoPanel;
|
private final StringFilterGroup infoPanel;
|
||||||
|
private final StringFilterGroup joinButton;
|
||||||
private final StringFilterGroup shelfHeader;
|
private final StringFilterGroup shelfHeader;
|
||||||
|
|
||||||
private final StringFilterGroup videoActionButton;
|
private final StringFilterGroup actionBar;
|
||||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
public ShortsFilter() {
|
public ShortsFilter() {
|
||||||
|
// Identifier components.
|
||||||
|
|
||||||
var shorts = new StringFilterGroup(
|
var shorts = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS,
|
null, // Setting is based on navigation state.
|
||||||
"shorts_shelf",
|
"shorts_shelf",
|
||||||
"inline_shorts",
|
"inline_shorts",
|
||||||
"shorts_grid",
|
"shorts_grid",
|
||||||
@ -42,7 +52,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
// 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(
|
shelfHeader = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS,
|
null,
|
||||||
"shelf_header.eml"
|
"shelf_header.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -54,17 +64,17 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
||||||
|
|
||||||
|
// Path components.
|
||||||
|
|
||||||
// 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.
|
||||||
shortsCompactFeedVideoPath = new StringFilterGroup(Settings.HIDE_SHORTS,
|
shortsCompactFeedVideoPath = new StringFilterGroup(null, "compact_video.eml");
|
||||||
"compact_video.eml");
|
|
||||||
// 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(Settings.HIDE_SHORTS,
|
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
||||||
"/frame0.jpg");
|
|
||||||
|
|
||||||
// Shorts player components.
|
// Shorts player components.
|
||||||
var joinButton = new StringFilterGroup(
|
joinButton = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
||||||
"sponsor_button"
|
"sponsor_button"
|
||||||
);
|
);
|
||||||
@ -84,6 +94,21 @@ public final class ShortsFilter extends Filter {
|
|||||||
REEL_CHANNEL_BAR_PATH
|
REEL_CHANNEL_BAR_PATH
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fullVideoLinkLabel = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL,
|
||||||
|
"reel_multi_format_link"
|
||||||
|
);
|
||||||
|
|
||||||
|
videoTitle = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_VIDEO_TITLE,
|
||||||
|
"shorts_video_title_item"
|
||||||
|
);
|
||||||
|
|
||||||
|
reelSoundMetadata = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
||||||
|
"reel_sound_metadata"
|
||||||
|
);
|
||||||
|
|
||||||
soundButton = new StringFilterGroup(
|
soundButton = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
||||||
"reel_pivot_button"
|
"reel_pivot_button"
|
||||||
@ -94,15 +119,26 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_info_panel_overview"
|
"shorts_info_panel_overview"
|
||||||
);
|
);
|
||||||
|
|
||||||
videoActionButton = new StringFilterGroup(
|
actionBar = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"ContainerType|shorts_video_action_button"
|
"shorts_action_bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
shortsCompactFeedVideoPath,
|
shortsCompactFeedVideoPath,
|
||||||
joinButton, subscribeButton, subscribeButtonPaused,
|
joinButton, subscribeButton, subscribeButtonPaused,
|
||||||
channelBar, soundButton, infoPanel, videoActionButton
|
channelBar, fullVideoLinkLabel, videoTitle, reelSoundMetadata,
|
||||||
|
soundButton, infoPanel, actionBar
|
||||||
|
);
|
||||||
|
|
||||||
|
var shortsLikeButton = new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_LIKE_BUTTON,
|
||||||
|
"shorts_like_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
var shortsDislikeButton = new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
|
||||||
|
"shorts_dislike_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
var shortsCommentButton = new ByteArrayFilterGroup(
|
var shortsCommentButton = new ByteArrayFilterGroup(
|
||||||
@ -120,7 +156,13 @@ public final class ShortsFilter extends Filter {
|
|||||||
"reel_remix_button"
|
"reel_remix_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
videoActionButtonGroupList.addAll(shortsCommentButton, shortsShareButton, shortsRemixButton);
|
videoActionButtonGroupList.addAll(
|
||||||
|
shortsLikeButton,
|
||||||
|
shortsDislikeButton,
|
||||||
|
shortsCommentButton,
|
||||||
|
shortsShareButton,
|
||||||
|
shortsRemixButton
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,18 +173,22 @@ public final class ShortsFilter extends Filter {
|
|||||||
if (matchedGroup == soundButton ||
|
if (matchedGroup == soundButton ||
|
||||||
matchedGroup == infoPanel ||
|
matchedGroup == infoPanel ||
|
||||||
matchedGroup == channelBar ||
|
matchedGroup == channelBar ||
|
||||||
|
matchedGroup == fullVideoLinkLabel ||
|
||||||
|
matchedGroup == videoTitle ||
|
||||||
|
matchedGroup == reelSoundMetadata ||
|
||||||
matchedGroup == subscribeButtonPaused
|
matchedGroup == subscribeButtonPaused
|
||||||
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
|
||||||
if (matchedGroup == shortsCompactFeedVideoPath) {
|
if (matchedGroup == shortsCompactFeedVideoPath) {
|
||||||
if (contentIndex == 0 && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
if (shouldHideShortsFeedItems() && contentIndex == 0
|
||||||
|
&& shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video action buttons (comment, share, remix) have the same path.
|
// Video action buttons (like, dislike, comment, share, remix) have the same path.
|
||||||
if (matchedGroup == videoActionButton) {
|
if (matchedGroup == actionBar) {
|
||||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
||||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
);
|
);
|
||||||
@ -151,25 +197,46 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
||||||
// to avoid false positives.
|
// to avoid false positives.
|
||||||
if (matchedGroup == subscribeButton) {
|
if (matchedGroup == subscribeButton ||
|
||||||
|
matchedGroup == joinButton
|
||||||
|
) {
|
||||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
|
if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
|
||||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
);
|
); // else, return false.
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if (matchedGroup == shelfHeader) {
|
} else {
|
||||||
// Because the header is used in watch history and possibly other places, check for the index,
|
// Feed/search path components.
|
||||||
// which is 0 when the shelf header is used for Shorts.
|
if (matchedGroup == shelfHeader) {
|
||||||
if (contentIndex != 0) return false;
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldHideShortsFeedItems()) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldHideShortsFeedItems() {
|
||||||
|
if (NavigationBar.isSearchBarActive()) { // Must check search first.
|
||||||
|
return Settings.HIDE_SHORTS_SEARCH.get();
|
||||||
|
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||||
|
|| NavigationBar.NavigationButton.HOME.isSelected()) {
|
||||||
|
return Settings.HIDE_SHORTS_HOME.get();
|
||||||
|
} else if (NavigationBar.NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
||||||
|
return Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
hideViewBy1dpUnderCondition(Settings.HIDE_SHORTS, shortsShelfView);
|
if (shouldHideShortsFeedItems()) {
|
||||||
|
Utils.hideViewByLayoutParams(shortsShelfView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Hide the buttons in older versions of YouTube. New versions use Litho.
|
// region Hide the buttons in older versions of YouTube. New versions use Litho.
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
package app.revanced.integrations.youtube.patches.spoof;
|
package app.revanced.integrations.youtube.patches.spoof;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofAppVersionPatch {
|
public class SpoofAppVersionPatch {
|
||||||
|
|
||||||
private static final boolean SPOOF_APP_VERSION_ENABLED;
|
private static final boolean SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get();
|
||||||
private static final String SPOOF_APP_VERSION_TARGET;
|
private static final String SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get();
|
||||||
|
|
||||||
static {
|
|
||||||
// TODO: remove this migration code
|
|
||||||
// Spoof targets below 17.33 that no longer reliably work.
|
|
||||||
if (Settings.SPOOF_APP_VERSION_TARGET.get().compareTo("17.33.01") < 0) {
|
|
||||||
Logger.printInfo(() -> "Resetting spoof app version target");
|
|
||||||
Settings.SPOOF_APP_VERSION_TARGET.resetToDefault();
|
|
||||||
}
|
|
||||||
// End migration
|
|
||||||
|
|
||||||
SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get();
|
|
||||||
SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point
|
* Injection point
|
||||||
@ -30,8 +16,8 @@ public class SpoofAppVersionPatch {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSpoofingToEqualOrLessThan(String version) {
|
public static boolean isSpoofingToLessThan(String version) {
|
||||||
return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) <= 0;
|
return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
package app.revanced.integrations.youtube.patches.spoof;
|
package app.revanced.integrations.youtube.patches.spoof;
|
||||||
|
|
||||||
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
|
||||||
import static app.revanced.integrations.shared.Utils.containsAny;
|
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
import static app.revanced.integrations.shared.Utils.containsAny;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
|
|
||||||
/** @noinspection unused*/
|
/** @noinspection unused*/
|
||||||
|
@Deprecated
|
||||||
public class SpoofSignaturePatch {
|
public class SpoofSignaturePatch {
|
||||||
/**
|
/**
|
||||||
* Parameter (also used by
|
* Parameter (also used by
|
||||||
@ -90,7 +89,7 @@ public class SpoofSignaturePatch {
|
|||||||
try {
|
try {
|
||||||
Logger.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
Logger.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
||||||
|
|
||||||
if (!Settings.SPOOF_SIGNATURE.get()) {
|
if (parameters == null || !Settings.SPOOF_SIGNATURE.get()) {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +97,7 @@ public class SpoofSignaturePatch {
|
|||||||
// For this reason, the player parameters of a clip are usually very long (150~300 characters).
|
// For this reason, the player parameters of a clip are usually very long (150~300 characters).
|
||||||
// Clips are 60 seconds or less in length, so no spoofing.
|
// Clips are 60 seconds or less in length, so no spoofing.
|
||||||
//noinspection AssignmentUsedAsCondition
|
//noinspection AssignmentUsedAsCondition
|
||||||
if (useOriginalStoryboardRenderer = parameters.length() > 150 || containsAny(parameters, CLIPS_PARAMETERS)) {
|
if (useOriginalStoryboardRenderer = parameters.length() > 150 || parameters.startsWith(CLIPS_PARAMETERS)) {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public final class StoryboardRenderer {
|
public final class StoryboardRenderer {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String spec;
|
private final String spec;
|
||||||
|
@ -10,6 +10,7 @@ import org.json.JSONObject;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
|
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
|
||||||
static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route(
|
static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route(
|
||||||
|
@ -19,6 +19,7 @@ import java.util.Objects;
|
|||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
import static app.revanced.integrations.youtube.patches.spoof.requests.PlayerRoutes.*;
|
import static app.revanced.integrations.youtube.patches.spoof.requests.PlayerRoutes.*;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class StoryboardRendererRequester {
|
public class StoryboardRendererRequester {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,7 +91,7 @@ public class ReturnYouTubeDislike {
|
|||||||
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
||||||
|
|
||||||
private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
|
private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
|
||||||
= SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.09.39");
|
= SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached lookup of all video ids.
|
* Cached lookup of all video ids.
|
||||||
|
@ -70,14 +70,16 @@ public class LicenseActivityHook {
|
|||||||
|
|
||||||
private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
|
private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
|
||||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||||
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof TextView));
|
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, false,
|
||||||
|
view -> view instanceof TextView));
|
||||||
toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UseCompatLoadingForDrawables")
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
private static void setBackButton(Activity activity) {
|
private static void setBackButton(Activity activity) {
|
||||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||||
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof ImageButton));
|
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false,
|
||||||
|
view -> view instanceof ImageButton));
|
||||||
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
||||||
? "yt_outline_arrow_left_white_24"
|
? "yt_outline_arrow_left_white_24"
|
||||||
: "yt_outline_arrow_left_black_24",
|
: "yt_outline_arrow_left_black_24",
|
||||||
|
@ -1,42 +1,32 @@
|
|||||||
package app.revanced.integrations.youtube.settings;
|
package app.revanced.integrations.youtube.settings;
|
||||||
|
|
||||||
import static java.lang.Boolean.FALSE;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import static java.lang.Boolean.TRUE;
|
import app.revanced.integrations.shared.settings.*;
|
||||||
import static app.revanced.integrations.shared.settings.Setting.migrateFromOldPreferences;
|
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
||||||
import static app.revanced.integrations.shared.settings.Setting.migrateOldSettingToNew;
|
import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||||
import static app.revanced.integrations.shared.settings.Setting.parent;
|
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
||||||
import static app.revanced.integrations.shared.settings.Setting.parentsAny;
|
|
||||||
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
|
|
||||||
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
|
||||||
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
|
||||||
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
import static app.revanced.integrations.shared.settings.Setting.*;
|
||||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.*;
|
||||||
import app.revanced.integrations.shared.settings.FloatSetting;
|
import static java.lang.Boolean.FALSE;
|
||||||
import app.revanced.integrations.shared.settings.IntegerSetting;
|
import static java.lang.Boolean.TRUE;
|
||||||
import app.revanced.integrations.shared.settings.LongSetting;
|
|
||||||
import app.revanced.integrations.shared.settings.Setting;
|
|
||||||
import app.revanced.integrations.shared.settings.StringSetting;
|
|
||||||
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
|
||||||
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
|
||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends BaseSettings {
|
||||||
// External downloader
|
// External downloader
|
||||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||||
|
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||||
"org.schabi.newpipe" /* NewPipe */, parent(EXTERNAL_DOWNLOADER));
|
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||||
|
|
||||||
// Copy video URL
|
// Copy video URL
|
||||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||||
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);
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
public static final BooleanSetting HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_hdr_auto_brightness", TRUE);
|
|
||||||
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE);
|
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", 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);
|
||||||
@ -45,6 +35,8 @@ public class Settings extends BaseSettings {
|
|||||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", 1.0f);
|
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", 1.0f);
|
||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
||||||
|
@Deprecated // Patch is obsolete and no longer works with 19.09+
|
||||||
|
public static final BooleanSetting HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_hdr_auto_brightness", TRUE);
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
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);
|
||||||
@ -72,7 +64,7 @@ public class Settings extends BaseSettings {
|
|||||||
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_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_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 BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", TRUE);
|
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
|
||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
||||||
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);
|
||||||
public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE);
|
public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE);
|
||||||
@ -106,6 +98,11 @@ public class Settings extends BaseSettings {
|
|||||||
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_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", TRUE);
|
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", 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);
|
||||||
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
|
||||||
|
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
||||||
|
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||||
public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
|
public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
|
||||||
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_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||||
@ -144,24 +141,32 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_TRANSCIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
public static final BooleanSetting HIDE_TRANSCIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||||
|
|
||||||
// Shorts
|
// Shorts
|
||||||
public static final BooleanSetting HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE, true);
|
@Deprecated public static final BooleanSetting DEPRECATED_HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_subscriptions", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_thanks_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_LIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_like_button", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", 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_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", 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_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_SOUND_METADATA_LABEL = new BooleanSetting("revanced_hide_shorts_sound_metadata_label", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true);
|
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true);
|
||||||
|
|
||||||
// Seekbar
|
// Seekbar
|
||||||
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE);
|
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE);
|
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE);
|
||||||
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", TRUE, true);
|
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_VALUE = new StringSetting("revanced_seekbar_custom_color_value", "#FF0000", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_VALUE = new StringSetting("revanced_seekbar_custom_color_value", "#FF0000", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
@ -204,8 +209,9 @@ public class Settings extends BaseSettings {
|
|||||||
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);
|
||||||
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 ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||||
public static final StringSetting ANNOUNCEMENT_CONSUMER = new StringSetting("revanced_announcement_consumer", "", false, false);
|
@Deprecated
|
||||||
public static final StringSetting ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", "");
|
public static final StringSetting DEPRECATED_ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", "");
|
||||||
|
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1);
|
||||||
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
||||||
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");
|
||||||
@ -229,6 +235,10 @@ public class Settings extends BaseSettings {
|
|||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
|
/**
|
||||||
|
* When enabled, share the debug logs with care.
|
||||||
|
* The buffer contains select user data, including the client ip address and information that could identify the YT account.
|
||||||
|
*/
|
||||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
||||||
|
|
||||||
// ReturnYoutubeDislike
|
// ReturnYoutubeDislike
|
||||||
@ -245,6 +255,7 @@ public class Settings extends BaseSettings {
|
|||||||
* Do not use directly, instead use {@link SponsorBlockSettings}
|
* Do not use directly, instead use {@link SponsorBlockSettings}
|
||||||
*/
|
*/
|
||||||
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "");
|
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "");
|
||||||
|
@Deprecated
|
||||||
public static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
|
public static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
|
||||||
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
||||||
@ -343,6 +354,26 @@ public class Settings extends BaseSettings {
|
|||||||
// and more time should be given for users who rarely upgrade.
|
// and more time should be given for users who rarely upgrade.
|
||||||
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
|
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
|
||||||
|
|
||||||
|
|
||||||
|
// Old spoof versions that no longer work reliably.
|
||||||
|
if (SpoofAppVersionPatch.isSpoofingToLessThan("17.33.00")) {
|
||||||
|
Logger.printInfo(() -> "Resetting spoof app version target");
|
||||||
|
Settings.SPOOF_APP_VERSION_TARGET.resetToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Remove any previously saved announcement consumer (a random generated string).
|
||||||
|
Setting.preferences.saveString("revanced_announcement_consumer", null);
|
||||||
|
|
||||||
|
// Shorts
|
||||||
|
if (DEPRECATED_HIDE_SHORTS.get()) {
|
||||||
|
Logger.printInfo(() -> "Migrating hide Shorts setting");
|
||||||
|
DEPRECATED_HIDE_SHORTS.resetToDefault();
|
||||||
|
HIDE_SHORTS_HOME.save(true);
|
||||||
|
HIDE_SHORTS_SUBSCRIPTIONS.save(true);
|
||||||
|
HIDE_SHORTS_SEARCH.save(true);
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package app.revanced.integrations.youtube.settings.preference;
|
package app.revanced.integrations.youtube.settings.preference;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
|
import app.revanced.integrations.youtube.patches.DownloadsPatch;
|
||||||
import app.revanced.integrations.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
import app.revanced.integrations.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
@ -12,14 +19,20 @@ import app.revanced.integrations.youtube.settings.Settings;
|
|||||||
* @noinspection deprecation
|
* @noinspection deprecation
|
||||||
*/
|
*/
|
||||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
super.initialize();
|
super.initialize();
|
||||||
|
|
||||||
// If the preference was included, then initialize it based on the available playback speed
|
try {
|
||||||
Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
|
// If the preference was included, then initialize it based on the available playback speed.
|
||||||
if (defaultSpeedPreference instanceof ListPreference) {
|
Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
|
||||||
CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
if (defaultSpeedPreference instanceof ListPreference) {
|
||||||
|
CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "initialize failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,184 @@
|
|||||||
|
package app.revanced.integrations.youtube.shared;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class NavigationBar {
|
||||||
|
private static volatile boolean searchbarIsActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void searchBarResultsViewLoaded(View searchbarResults) {
|
||||||
|
searchbarResults.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||||
|
final boolean isActive = searchbarResults.getParent() != null;
|
||||||
|
|
||||||
|
if (searchbarIsActive != isActive) {
|
||||||
|
searchbarIsActive = isActive;
|
||||||
|
Logger.printDebug(() -> "searchbarIsActive: " + isActive);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSearchBarActive() {
|
||||||
|
return searchbarIsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static volatile String lastYTNavigationEnumName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setLastAppNavigationEnum(@Nullable Enum ytNavigationEnumName) {
|
||||||
|
if (ytNavigationEnumName != null) {
|
||||||
|
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
||||||
|
try {
|
||||||
|
String lastEnumName = lastYTNavigationEnumName;
|
||||||
|
for (NavigationButton button : NavigationButton.values()) {
|
||||||
|
if (button.ytEnumName.equals(lastEnumName)) {
|
||||||
|
ImageView imageView = Utils.getChildView((ViewGroup) navigationButtonGroup,
|
||||||
|
true, view -> view instanceof ImageView);
|
||||||
|
|
||||||
|
if (imageView != null) {
|
||||||
|
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
||||||
|
|
||||||
|
button.imageViewRef = new WeakReference<>(imageView);
|
||||||
|
navigationTabCreatedCallback(button, navigationButtonGroup);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Log the unknown tab as exception level, only if debug is enabled.
|
||||||
|
// This is because unknown tabs do no harm, and it's only relevant to developers.
|
||||||
|
if (Settings.DEBUG.get()) {
|
||||||
|
Logger.printException(() -> "Unknown tab: " + lastEnumName
|
||||||
|
+ " view: " + navigationButtonGroup.getClass());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "navigationTabLoaded failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Unique hook just for the 'Create' and 'You' tab.
|
||||||
|
*/
|
||||||
|
public static void navigationImageResourceTabLoaded(View view) {
|
||||||
|
// 'You' tab has no YT enum name and the enum hook is not called for it.
|
||||||
|
// Compare the last enum to figure out which tab this actually is.
|
||||||
|
if (CREATE.ytEnumName.equals(lastYTNavigationEnumName)) {
|
||||||
|
navigationTabLoaded(view);
|
||||||
|
} else {
|
||||||
|
lastYTNavigationEnumName = NavigationButton.LIBRARY_YOU.ytEnumName;
|
||||||
|
navigationTabLoaded(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection EmptyMethod*/
|
||||||
|
private static void navigationTabCreatedCallback(NavigationBar.NavigationButton button, View tabView) {
|
||||||
|
// Code is added during patching.
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NavigationButton {
|
||||||
|
HOME("PIVOT_HOME"),
|
||||||
|
SHORTS("TAB_SHORTS"),
|
||||||
|
/**
|
||||||
|
* Create new video tab.
|
||||||
|
*
|
||||||
|
* {@link #isSelected()} always returns false, even if the create video UI is on screen.
|
||||||
|
*/
|
||||||
|
CREATE("CREATION_TAB_LARGE"),
|
||||||
|
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
||||||
|
/**
|
||||||
|
* Notifications tab. Only present when
|
||||||
|
* {@link Settings#SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON} is active.
|
||||||
|
*/
|
||||||
|
ACTIVITY("TAB_ACTIVITY"),
|
||||||
|
/**
|
||||||
|
* Library tab when the user is not logged in.
|
||||||
|
*/
|
||||||
|
LIBRARY_LOGGED_OUT("ACCOUNT_CIRCLE"),
|
||||||
|
/**
|
||||||
|
* User is logged in with incognito mode enabled.
|
||||||
|
*/
|
||||||
|
LIBRARY_INCOGNITO("INCOGNITO_CIRCLE"),
|
||||||
|
/**
|
||||||
|
* Old library tab (pre 'You' layout), only present when version spoofing.
|
||||||
|
*/
|
||||||
|
LIBRARY_OLD_UI("VIDEO_LIBRARY_WHITE"),
|
||||||
|
/**
|
||||||
|
* 'You' library tab that is sometimes momentarily loaded.
|
||||||
|
* When this is loaded, {@link #LIBRARY_YOU} is also present.
|
||||||
|
*
|
||||||
|
* This might be a temporary tab while the user profile photo is loading,
|
||||||
|
* but its exact purpose is not entirely clear.
|
||||||
|
*/
|
||||||
|
LIBRARY_PIVOT_UNKNOWN("PIVOT_LIBRARY"),
|
||||||
|
/**
|
||||||
|
* Modern library tab with 'You' layout.
|
||||||
|
*/
|
||||||
|
// The hooked YT code does not use an enum, and a dummy name is used here.
|
||||||
|
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The active navigation tab.
|
||||||
|
* If the user is in the create new video UI, this returns NULL.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static NavigationButton getSelectedNavigationButton() {
|
||||||
|
for (NavigationButton button : values()) {
|
||||||
|
if (button.isSelected()) return button;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the currently selected tab is a 'You' or library type.
|
||||||
|
* Covers all known app states including incognito mode and version spoofing.
|
||||||
|
*/
|
||||||
|
public static boolean libraryOrYouTabIsSelected() {
|
||||||
|
return LIBRARY_YOU.isSelected() || LIBRARY_PIVOT_UNKNOWN.isSelected()
|
||||||
|
|| LIBRARY_OLD_UI.isSelected() || LIBRARY_INCOGNITO.isSelected()
|
||||||
|
|| LIBRARY_LOGGED_OUT.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube enum name for this tab.
|
||||||
|
*/
|
||||||
|
private final String ytEnumName;
|
||||||
|
private volatile WeakReference<ImageView> imageViewRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
NavigationButton(String ytEnumName) {
|
||||||
|
this.ytEnumName = ytEnumName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
ImageView view = imageViewRef.get();
|
||||||
|
return view != null && view.isSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ import java.lang.ref.WeakReference
|
|||||||
* @param activity activity that contains the controls_layout view
|
* @param activity activity that contains the controls_layout view
|
||||||
*/
|
*/
|
||||||
class PlayerControlsVisibilityObserverImpl(
|
class PlayerControlsVisibilityObserverImpl(
|
||||||
private val activity: Activity
|
private val activity: Activity,
|
||||||
) : PlayerControlsVisibilityObserver {
|
) : PlayerControlsVisibilityObserver {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,8 +2,8 @@ package app.revanced.integrations.youtube.shared
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
|
||||||
import app.revanced.integrations.youtube.Event
|
import app.revanced.integrations.youtube.Event
|
||||||
|
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hooking class for player overlays
|
* hooking class for player overlays
|
||||||
@ -42,8 +42,8 @@ object PlayerOverlays {
|
|||||||
ChildrenChangeEventArgs(
|
ChildrenChangeEventArgs(
|
||||||
parent,
|
parent,
|
||||||
child,
|
child,
|
||||||
false
|
false,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,8 +54,8 @@ object PlayerOverlays {
|
|||||||
ChildrenChangeEventArgs(
|
ChildrenChangeEventArgs(
|
||||||
parent,
|
parent,
|
||||||
child,
|
child,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,15 +69,15 @@ object PlayerOverlays {
|
|||||||
oldLeft,
|
oldLeft,
|
||||||
oldTop,
|
oldTop,
|
||||||
oldRight - oldLeft,
|
oldRight - oldLeft,
|
||||||
oldBottom - oldTop
|
oldBottom - oldTop,
|
||||||
),
|
),
|
||||||
Rectangle(
|
Rectangle(
|
||||||
newLeft,
|
newLeft,
|
||||||
newTop,
|
newTop,
|
||||||
newRight - newLeft,
|
newRight - newLeft,
|
||||||
newBottom - newTop
|
newBottom - newTop,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,11 +87,11 @@ object PlayerOverlays {
|
|||||||
data class ChildrenChangeEventArgs(
|
data class ChildrenChangeEventArgs(
|
||||||
val overlaysLayout: ViewGroup,
|
val overlaysLayout: ViewGroup,
|
||||||
val childView: View,
|
val childView: View,
|
||||||
val wasChildRemoved: Boolean
|
val wasChildRemoved: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class LayoutChangeEventArgs(
|
data class LayoutChangeEventArgs(
|
||||||
val overlaysLayout: ViewGroup,
|
val overlaysLayout: ViewGroup,
|
||||||
val oldRect: Rectangle,
|
val oldRect: Rectangle,
|
||||||
val newRect: Rectangle
|
val newRect: Rectangle,
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package app.revanced.integrations.youtube.shared
|
package app.revanced.integrations.youtube.shared
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.patches.VideoInformation
|
|
||||||
import app.revanced.integrations.youtube.Event
|
|
||||||
import app.revanced.integrations.shared.Logger
|
import app.revanced.integrations.shared.Logger
|
||||||
|
import app.revanced.integrations.youtube.Event
|
||||||
|
import app.revanced.integrations.youtube.patches.VideoInformation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main player type.
|
* Main player type.
|
||||||
@ -12,11 +12,13 @@ enum class PlayerType {
|
|||||||
* Either no video, or a Short is playing.
|
* Either no video, or a Short is playing.
|
||||||
*/
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Short is playing. Occurs if a regular video is first opened
|
* A Short is playing. Occurs if a regular video is first opened
|
||||||
* and then a Short is opened (without first closing the regular video).
|
* and then a Short is opened (without first closing the regular video).
|
||||||
*/
|
*/
|
||||||
HIDDEN,
|
HIDDEN,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A regular video is minimized.
|
* A regular video is minimized.
|
||||||
*
|
*
|
||||||
@ -28,6 +30,7 @@ enum class PlayerType {
|
|||||||
WATCH_WHILE_FULLSCREEN,
|
WATCH_WHILE_FULLSCREEN,
|
||||||
WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN,
|
WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN,
|
||||||
WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED,
|
WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player is either sliding to [HIDDEN] state because a Short was opened while a regular video is on screen.
|
* Player is either sliding to [HIDDEN] state because a Short was opened while a regular video is on screen.
|
||||||
* OR
|
* OR
|
||||||
@ -35,12 +38,14 @@ enum class PlayerType {
|
|||||||
*/
|
*/
|
||||||
WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED,
|
WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED,
|
||||||
WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED,
|
WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Home feed video playback.
|
* Home feed video playback.
|
||||||
*/
|
*/
|
||||||
INLINE_MINIMAL,
|
INLINE_MINIMAL,
|
||||||
VIRTUAL_REALITY_FULLSCREEN,
|
VIRTUAL_REALITY_FULLSCREEN,
|
||||||
WATCH_WHILE_PICTURE_IN_PICTURE;
|
WATCH_WHILE_PICTURE_IN_PICTURE,
|
||||||
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@ -67,6 +72,7 @@ enum class PlayerType {
|
|||||||
currentPlayerType = value
|
currentPlayerType = value
|
||||||
onChange(currentPlayerType)
|
onChange(currentPlayerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Volatile // value is read/write from different threads
|
@Volatile // value is read/write from different threads
|
||||||
private var currentPlayerType = NONE
|
private var currentPlayerType = NONE
|
||||||
|
|
||||||
@ -127,4 +133,7 @@ enum class PlayerType {
|
|||||||
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isMaximizedOrFullscreen(): Boolean {
|
||||||
|
return this == WATCH_WHILE_MAXIMIZED || this == WATCH_WHILE_FULLSCREEN
|
||||||
|
}
|
||||||
}
|
}
|
@ -12,10 +12,13 @@ enum class VideoState {
|
|||||||
PAUSED,
|
PAUSED,
|
||||||
RECOVERABLE_ERROR,
|
RECOVERABLE_ERROR,
|
||||||
UNRECOVERABLE_ERROR,
|
UNRECOVERABLE_ERROR,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see [VideoInformation.isAtEndOfVideo]
|
* @see [VideoInformation.isAtEndOfVideo]
|
||||||
*/
|
*/
|
||||||
ENDED;
|
ENDED,
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@ -43,6 +46,6 @@ enum class VideoState {
|
|||||||
currentVideoState = value
|
currentVideoState = value
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentVideoState : VideoState? = null
|
private var currentVideoState: VideoState? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ import app.revanced.integrations.youtube.shared.PlayerType
|
|||||||
* @param context the context to create in
|
* @param context the context to create in
|
||||||
*/
|
*/
|
||||||
class SwipeControlsConfigurationProvider(
|
class SwipeControlsConfigurationProvider(
|
||||||
private val context: Context
|
private val context: Context,
|
||||||
) {
|
) {
|
||||||
//region swipe enable
|
//region swipe enable
|
||||||
/**
|
/**
|
||||||
|
@ -6,6 +6,8 @@ import android.os.Bundle
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import app.revanced.integrations.shared.Logger.printDebug
|
||||||
|
import app.revanced.integrations.shared.Logger.printException
|
||||||
import app.revanced.integrations.youtube.shared.PlayerType
|
import app.revanced.integrations.youtube.shared.PlayerType
|
||||||
import app.revanced.integrations.youtube.swipecontrols.controller.AudioVolumeController
|
import app.revanced.integrations.youtube.swipecontrols.controller.AudioVolumeController
|
||||||
import app.revanced.integrations.youtube.swipecontrols.controller.ScreenBrightnessController
|
import app.revanced.integrations.youtube.swipecontrols.controller.ScreenBrightnessController
|
||||||
@ -16,8 +18,6 @@ import app.revanced.integrations.youtube.swipecontrols.controller.gesture.PressT
|
|||||||
import app.revanced.integrations.youtube.swipecontrols.controller.gesture.core.GestureController
|
import app.revanced.integrations.youtube.swipecontrols.controller.gesture.core.GestureController
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
||||||
import app.revanced.integrations.youtube.swipecontrols.views.SwipeControlsOverlayLayout
|
import app.revanced.integrations.youtube.swipecontrols.views.SwipeControlsOverlayLayout
|
||||||
import app.revanced.integrations.shared.Logger.printDebug
|
|
||||||
import app.revanced.integrations.shared.Logger.printException
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,14 +80,18 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
ensureInitialized()
|
ensureInitialized()
|
||||||
return if ((ev != null) && gesture.submitTouchEvent(ev)) true else {
|
return if ((ev != null) && gesture.submitTouchEvent(ev)) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
super.dispatchTouchEvent(ev)
|
super.dispatchTouchEvent(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(ev: KeyEvent?): Boolean {
|
override fun dispatchKeyEvent(ev: KeyEvent?): Boolean {
|
||||||
ensureInitialized()
|
ensureInitialized()
|
||||||
return if ((ev != null) && keys.onKeyEvent(ev)) true else {
|
return if ((ev != null) && keys.onKeyEvent(ev)) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
super.dispatchKeyEvent(ev)
|
super.dispatchKeyEvent(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +143,7 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
contentRoot.x.toInt(),
|
contentRoot.x.toInt(),
|
||||||
contentRoot.y.toInt(),
|
contentRoot.y.toInt(),
|
||||||
contentRoot.width,
|
contentRoot.width,
|
||||||
contentRoot.height
|
contentRoot.height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +161,7 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
* (re) attaches swipe overlays
|
* (re) attaches swipe overlays
|
||||||
*/
|
*/
|
||||||
private fun reAttachOverlays() {
|
private fun reAttachOverlays() {
|
||||||
printDebug{ "attaching swipe controls overlay" }
|
printDebug { "attaching swipe controls overlay" }
|
||||||
contentRoot.removeView(overlay)
|
contentRoot.removeView(overlay)
|
||||||
contentRoot.addView(overlay)
|
contentRoot.addView(overlay)
|
||||||
}
|
}
|
||||||
@ -168,7 +172,7 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
* @param type the new player type
|
* @param type the new player type
|
||||||
*/
|
*/
|
||||||
private fun onPlayerTypeChanged(type: PlayerType) {
|
private fun onPlayerTypeChanged(type: PlayerType) {
|
||||||
if (config.shouldSaveAndRestoreBrightness)
|
if (config.shouldSaveAndRestoreBrightness) {
|
||||||
when (type) {
|
when (type) {
|
||||||
PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore()
|
PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore()
|
||||||
else -> {
|
else -> {
|
||||||
@ -176,29 +180,38 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
screen?.restoreDefaultBrightness()
|
screen?.restoreDefaultBrightness()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create the audio volume controller
|
* create the audio volume controller
|
||||||
*/
|
*/
|
||||||
private fun createAudioController() =
|
private fun createAudioController() =
|
||||||
if (config.enableVolumeControls)
|
if (config.enableVolumeControls) {
|
||||||
AudioVolumeController(this) else null
|
AudioVolumeController(this)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create the screen brightness controller instance
|
* create the screen brightness controller instance
|
||||||
*/
|
*/
|
||||||
private fun createScreenController() =
|
private fun createScreenController() =
|
||||||
if (config.enableBrightnessControl)
|
if (config.enableBrightnessControl) {
|
||||||
ScreenBrightnessController(this) else null
|
ScreenBrightnessController(this)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create the gesture controller based on settings
|
* create the gesture controller based on settings
|
||||||
*/
|
*/
|
||||||
private fun createGestureController() =
|
private fun createGestureController() =
|
||||||
if (config.shouldEnablePressToSwipe)
|
if (config.shouldEnablePressToSwipe) {
|
||||||
PressToSwipeController(this)
|
PressToSwipeController(this)
|
||||||
else ClassicSwipeController(this)
|
} else {
|
||||||
|
ClassicSwipeController(this)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
@ -3,8 +3,8 @@ package app.revanced.integrations.youtube.swipecontrols.controller
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
|
||||||
import app.revanced.integrations.shared.Logger.printException
|
import app.revanced.integrations.shared.Logger.printException
|
||||||
|
import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,7 +15,7 @@ import kotlin.properties.Delegates
|
|||||||
*/
|
*/
|
||||||
class AudioVolumeController(
|
class AudioVolumeController(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val targetStream: Int = AudioManager.STREAM_MUSIC
|
private val targetStream: Int = AudioManager.STREAM_MUSIC,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,9 +34,13 @@ class AudioVolumeController(
|
|||||||
audioManager = mgr
|
audioManager = mgr
|
||||||
maximumVolumeIndex = audioManager.getStreamMaxVolume(targetStream)
|
maximumVolumeIndex = audioManager.getStreamMaxVolume(targetStream)
|
||||||
minimumVolumeIndex =
|
minimumVolumeIndex =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) audioManager.getStreamMinVolume(
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
targetStream
|
audioManager.getStreamMinVolume(
|
||||||
) else 0
|
targetStream,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
|||||||
* @param host the host activity of which the brightness is adjusted
|
* @param host the host activity of which the brightness is adjusted
|
||||||
*/
|
*/
|
||||||
class ScreenBrightnessController(
|
class ScreenBrightnessController(
|
||||||
private val host: Activity
|
private val host: Activity,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* screen brightness saved by [save]
|
* screen brightness saved by [save]
|
||||||
|
@ -3,9 +3,9 @@ package app.revanced.integrations.youtube.swipecontrols.controller
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import app.revanced.integrations.shared.Utils
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension
|
import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension
|
||||||
import app.revanced.integrations.shared.Utils
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +36,7 @@ import kotlin.math.min
|
|||||||
@Suppress("PrivatePropertyName")
|
@Suppress("PrivatePropertyName")
|
||||||
class SwipeZonesController(
|
class SwipeZonesController(
|
||||||
private val host: Activity,
|
private val host: Activity,
|
||||||
private val fallbackScreenRect: () -> Rectangle
|
private val fallbackScreenRect: () -> Rectangle,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 20dp, in pixels
|
* 20dp, in pixels
|
||||||
@ -74,7 +74,7 @@ class SwipeZonesController(
|
|||||||
p.x + _20dp,
|
p.x + _20dp,
|
||||||
p.y + _40dp,
|
p.y + _40dp,
|
||||||
p.width - _20dp,
|
p.width - _20dp,
|
||||||
p.height - _20dp - _80dp
|
p.height - _20dp - _80dp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ class SwipeZonesController(
|
|||||||
eRect.right - zoneWidth,
|
eRect.right - zoneWidth,
|
||||||
eRect.top,
|
eRect.top,
|
||||||
zoneWidth,
|
zoneWidth,
|
||||||
eRect.height
|
eRect.height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ class SwipeZonesController(
|
|||||||
effectiveSwipeRect.left,
|
effectiveSwipeRect.left,
|
||||||
effectiveSwipeRect.top,
|
effectiveSwipeRect.top,
|
||||||
zoneWidth,
|
zoneWidth,
|
||||||
effectiveSwipeRect.height
|
effectiveSwipeRect.height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ class SwipeZonesController(
|
|||||||
playerView.x.toInt(),
|
playerView.x.toInt(),
|
||||||
playerView.y.toInt(),
|
playerView.y.toInt(),
|
||||||
min(playerView.width, playerWidthWithPadding),
|
min(playerView.width, playerWidthWithPadding),
|
||||||
playerView.height
|
playerView.height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity
|
|||||||
* @param controller main controller instance
|
* @param controller main controller instance
|
||||||
*/
|
*/
|
||||||
class VolumeKeysController(
|
class VolumeKeysController(
|
||||||
private val controller: SwipeControlsHostActivity
|
private val controller: SwipeControlsHostActivity,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* key event handler
|
* key event handler
|
||||||
@ -18,7 +18,7 @@ class VolumeKeysController(
|
|||||||
* @return consume the event?
|
* @return consume the event?
|
||||||
*/
|
*/
|
||||||
fun onKeyEvent(event: KeyEvent): Boolean {
|
fun onKeyEvent(event: KeyEvent): Boolean {
|
||||||
if(!controller.config.overwriteVolumeKeyControls) {
|
if (!controller.config.overwriteVolumeKeyControls) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.toPoint
|
|||||||
* @param controller reference to the main swipe controller
|
* @param controller reference to the main swipe controller
|
||||||
*/
|
*/
|
||||||
class ClassicSwipeController(
|
class ClassicSwipeController(
|
||||||
private val controller: SwipeControlsHostActivity
|
private val controller: SwipeControlsHostActivity,
|
||||||
) : BaseGestureController(controller),
|
) : BaseGestureController(controller),
|
||||||
PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) {
|
PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) {
|
||||||
/**
|
/**
|
||||||
@ -27,10 +27,16 @@ class ClassicSwipeController(
|
|||||||
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL
|
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL
|
||||||
|
|
||||||
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
|
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
|
||||||
val inVolumeZone = if (controller.config.enableVolumeControls)
|
val inVolumeZone = if (controller.config.enableVolumeControls) {
|
||||||
(motionEvent.toPoint() in controller.zones.volume) else false
|
(motionEvent.toPoint() in controller.zones.volume)
|
||||||
val inBrightnessZone = if (controller.config.enableBrightnessControl)
|
} else {
|
||||||
(motionEvent.toPoint() in controller.zones.brightness) else false
|
false
|
||||||
|
}
|
||||||
|
val inBrightnessZone = if (controller.config.enableBrightnessControl) {
|
||||||
|
(motionEvent.toPoint() in controller.zones.brightness)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
return inVolumeZone || inBrightnessZone
|
return inVolumeZone || inBrightnessZone
|
||||||
}
|
}
|
||||||
@ -92,7 +98,7 @@ class ClassicSwipeController(
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Double,
|
distanceX: Double,
|
||||||
distanceY: Double
|
distanceY: Double,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// cancel if not vertical
|
// cancel if not vertical
|
||||||
if (currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
|
if (currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
|
||||||
|
@ -13,7 +13,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.toPoint
|
|||||||
* @param controller reference to the main swipe controller
|
* @param controller reference to the main swipe controller
|
||||||
*/
|
*/
|
||||||
class PressToSwipeController(
|
class PressToSwipeController(
|
||||||
private val controller: SwipeControlsHostActivity
|
private val controller: SwipeControlsHostActivity,
|
||||||
) : BaseGestureController(controller) {
|
) : BaseGestureController(controller) {
|
||||||
/**
|
/**
|
||||||
* monitors if the user is currently in a swipe session.
|
* monitors if the user is currently in a swipe session.
|
||||||
@ -26,10 +26,16 @@ class PressToSwipeController(
|
|||||||
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean = false
|
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean = false
|
||||||
|
|
||||||
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
|
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
|
||||||
val inVolumeZone = if (controller.config.enableVolumeControls)
|
val inVolumeZone = if (controller.config.enableVolumeControls) {
|
||||||
(motionEvent.toPoint() in controller.zones.volume) else false
|
(motionEvent.toPoint() in controller.zones.volume)
|
||||||
val inBrightnessZone = if (controller.config.enableBrightnessControl)
|
} else {
|
||||||
(motionEvent.toPoint() in controller.zones.brightness) else false
|
false
|
||||||
|
}
|
||||||
|
val inBrightnessZone = if (controller.config.enableBrightnessControl) {
|
||||||
|
(motionEvent.toPoint() in controller.zones.brightness)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
return inVolumeZone || inBrightnessZone
|
return inVolumeZone || inBrightnessZone
|
||||||
}
|
}
|
||||||
@ -53,7 +59,7 @@ class PressToSwipeController(
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Double,
|
distanceX: Double,
|
||||||
distanceY: Double
|
distanceY: Double,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// cancel if not in swipe session or vertical
|
// cancel if not in swipe session or vertical
|
||||||
if (!isInSwipeSession || currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
|
if (!isInSwipeSession || currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
|
||||||
|
@ -11,11 +11,11 @@ import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity
|
|||||||
* @param controller reference to the main swipe controller
|
* @param controller reference to the main swipe controller
|
||||||
*/
|
*/
|
||||||
abstract class BaseGestureController(
|
abstract class BaseGestureController(
|
||||||
private val controller: SwipeControlsHostActivity
|
private val controller: SwipeControlsHostActivity,
|
||||||
) : GestureController,
|
) : GestureController,
|
||||||
GestureDetector.SimpleOnGestureListener(),
|
GestureDetector.SimpleOnGestureListener(),
|
||||||
SwipeDetector by SwipeDetectorImpl(
|
SwipeDetector by SwipeDetectorImpl(
|
||||||
controller.config.swipeMagnitudeThreshold.toDouble()
|
controller.config.swipeMagnitudeThreshold.toDouble(),
|
||||||
),
|
),
|
||||||
VolumeAndBrightnessScroller by VolumeAndBrightnessScrollerImpl(
|
VolumeAndBrightnessScroller by VolumeAndBrightnessScrollerImpl(
|
||||||
controller,
|
controller,
|
||||||
@ -23,7 +23,7 @@ abstract class BaseGestureController(
|
|||||||
controller.screen,
|
controller.screen,
|
||||||
controller.overlay,
|
controller.overlay,
|
||||||
10,
|
10,
|
||||||
1
|
1,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,7 +85,7 @@ abstract class BaseGestureController(
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Float,
|
distanceX: Float,
|
||||||
distanceY: Float
|
distanceY: Float,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// submit to swipe detector
|
// submit to swipe detector
|
||||||
submitForSwipe(from, to, distanceX, distanceY)
|
submitForSwipe(from, to, distanceX, distanceY)
|
||||||
@ -96,7 +96,7 @@ abstract class BaseGestureController(
|
|||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
distanceX.toDouble(),
|
distanceX.toDouble(),
|
||||||
distanceY.toDouble()
|
distanceY.toDouble(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// if the swipe was consumed, cancel downstream events once
|
// if the swipe was consumed, cancel downstream events once
|
||||||
@ -110,7 +110,9 @@ abstract class BaseGestureController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
consumed
|
consumed
|
||||||
} else false
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +151,6 @@ abstract class BaseGestureController(
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Double,
|
distanceX: Double,
|
||||||
distanceY: Double
|
distanceY: Double,
|
||||||
): Boolean
|
): Boolean
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ interface SwipeDetector {
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Float,
|
distanceX: Float,
|
||||||
distanceY: Float
|
distanceY: Float,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,7 +50,7 @@ interface SwipeDetector {
|
|||||||
/**
|
/**
|
||||||
* swipe along the Y- Axes
|
* swipe along the Y- Axes
|
||||||
*/
|
*/
|
||||||
VERTICAL
|
VERTICAL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ interface SwipeDetector {
|
|||||||
* @param swipeMagnitudeThreshold minimum magnitude before a swipe is detected as such
|
* @param swipeMagnitudeThreshold minimum magnitude before a swipe is detected as such
|
||||||
*/
|
*/
|
||||||
class SwipeDetectorImpl(
|
class SwipeDetectorImpl(
|
||||||
private val swipeMagnitudeThreshold: Double
|
private val swipeMagnitudeThreshold: Double,
|
||||||
) : SwipeDetector {
|
) : SwipeDetector {
|
||||||
override var currentSwipe = SwipeDetector.SwipeDirection.NONE
|
override var currentSwipe = SwipeDetector.SwipeDirection.NONE
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class SwipeDetectorImpl(
|
|||||||
from: MotionEvent,
|
from: MotionEvent,
|
||||||
to: MotionEvent,
|
to: MotionEvent,
|
||||||
distanceX: Float,
|
distanceX: Float,
|
||||||
distanceY: Float
|
distanceY: Float,
|
||||||
) {
|
) {
|
||||||
if (currentSwipe == SwipeDetector.SwipeDirection.NONE) {
|
if (currentSwipe == SwipeDetector.SwipeDirection.NONE) {
|
||||||
// no swipe direction was detected yet, try to detect one
|
// no swipe direction was detected yet, try to detect one
|
||||||
|
@ -48,7 +48,7 @@ class VolumeAndBrightnessScrollerImpl(
|
|||||||
private val screenController: ScreenBrightnessController?,
|
private val screenController: ScreenBrightnessController?,
|
||||||
private val overlayController: SwipeControlsOverlay,
|
private val overlayController: SwipeControlsOverlay,
|
||||||
volumeDistance: Int = 10,
|
volumeDistance: Int = 10,
|
||||||
brightnessDistance: Int = 1
|
brightnessDistance: Int = 1,
|
||||||
) : VolumeAndBrightnessScroller {
|
) : VolumeAndBrightnessScroller {
|
||||||
|
|
||||||
// region volume
|
// region volume
|
||||||
@ -56,8 +56,8 @@ class VolumeAndBrightnessScrollerImpl(
|
|||||||
ScrollDistanceHelper(
|
ScrollDistanceHelper(
|
||||||
volumeDistance.applyDimension(
|
volumeDistance.applyDimension(
|
||||||
context,
|
context,
|
||||||
TypedValue.COMPLEX_UNIT_DIP
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
)
|
),
|
||||||
) { _, _, direction ->
|
) { _, _, direction ->
|
||||||
volumeController?.run {
|
volumeController?.run {
|
||||||
volume += direction
|
volume += direction
|
||||||
@ -73,8 +73,8 @@ class VolumeAndBrightnessScrollerImpl(
|
|||||||
ScrollDistanceHelper(
|
ScrollDistanceHelper(
|
||||||
brightnessDistance.applyDimension(
|
brightnessDistance.applyDimension(
|
||||||
context,
|
context,
|
||||||
TypedValue.COMPLEX_UNIT_DIP
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
)
|
),
|
||||||
) { _, _, direction ->
|
) { _, _, direction ->
|
||||||
screenController?.run {
|
screenController?.run {
|
||||||
if (screenBrightness > 0 || direction > 0) {
|
if (screenBrightness > 0 || direction > 0) {
|
||||||
|
@ -7,7 +7,7 @@ import android.view.MotionEvent
|
|||||||
*/
|
*/
|
||||||
data class Point(
|
data class Point(
|
||||||
val x: Int,
|
val x: Int,
|
||||||
val y: Int
|
val y: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,7 @@ data class Rectangle(
|
|||||||
val x: Int,
|
val x: Int,
|
||||||
val y: Int,
|
val y: Int,
|
||||||
val width: Int,
|
val width: Int,
|
||||||
val height: Int
|
val height: Int,
|
||||||
) {
|
) {
|
||||||
val left = x
|
val left = x
|
||||||
val right = x + width
|
val right = x + width
|
||||||
@ -15,7 +15,6 @@ data class Rectangle(
|
|||||||
val bottom = y + height
|
val bottom = y + height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the point within this rectangle?
|
* is the point within this rectangle?
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +11,7 @@ import kotlin.math.sign
|
|||||||
*/
|
*/
|
||||||
class ScrollDistanceHelper(
|
class ScrollDistanceHelper(
|
||||||
private val unitDistance: Int,
|
private val unitDistance: Int,
|
||||||
private val callback: (oldDistance: Double, newDistance: Double, direction: Int) -> Unit
|
private val callback: (oldDistance: Double, newDistance: Double, direction: Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +35,7 @@ class ScrollDistanceHelper(
|
|||||||
callback.invoke(
|
callback.invoke(
|
||||||
oldDistance,
|
oldDistance,
|
||||||
scrolledDistance,
|
scrolledDistance,
|
||||||
sign(scrolledDistance).toInt()
|
sign(scrolledDistance).toInt(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ fun Int.applyDimension(context: Context, unit: Int): Int {
|
|||||||
return TypedValue.applyDimension(
|
return TypedValue.applyDimension(
|
||||||
unit,
|
unit,
|
||||||
this.toFloat(),
|
this.toFloat(),
|
||||||
context.resources.displayMetrics
|
context.resources.displayMetrics,
|
||||||
).roundToInt()
|
).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import app.revanced.integrations.shared.Utils
|
||||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider
|
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay
|
import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension
|
import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension
|
||||||
import app.revanced.integrations.shared.Utils
|
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +24,7 @@ import kotlin.math.round
|
|||||||
*/
|
*/
|
||||||
class SwipeControlsOverlayLayout(
|
class SwipeControlsOverlayLayout(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val config: SwipeControlsConfigurationProvider
|
private val config: SwipeControlsConfigurationProvider,
|
||||||
) : RelativeLayout(context), SwipeControlsOverlay {
|
) : RelativeLayout(context), SwipeControlsOverlay {
|
||||||
/**
|
/**
|
||||||
* DO NOT use this, for tools only
|
* DO NOT use this, for tools only
|
||||||
@ -40,14 +40,14 @@ class SwipeControlsOverlayLayout(
|
|||||||
private fun getDrawable(name: String, width: Int, height: Int): Drawable {
|
private fun getDrawable(name: String, width: Int, height: Int): Drawable {
|
||||||
return resources.getDrawable(
|
return resources.getDrawable(
|
||||||
Utils.getResourceIdentifier(context, name, "drawable"),
|
Utils.getResourceIdentifier(context, name, "drawable"),
|
||||||
context.theme
|
context.theme,
|
||||||
).apply {
|
).apply {
|
||||||
setTint(config.overlayForegroundColor)
|
setTint(config.overlayForegroundColor)
|
||||||
setBounds(
|
setBounds(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
width,
|
width,
|
||||||
height
|
height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,14 +59,14 @@ class SwipeControlsOverlayLayout(
|
|||||||
feedbackTextView = TextView(context).apply {
|
feedbackTextView = TextView(context).apply {
|
||||||
layoutParams = LayoutParams(
|
layoutParams = LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
).apply {
|
).apply {
|
||||||
addRule(CENTER_IN_PARENT, TRUE)
|
addRule(CENTER_IN_PARENT, TRUE)
|
||||||
setPadding(
|
setPadding(
|
||||||
feedbackTextViewPadding,
|
feedbackTextViewPadding,
|
||||||
feedbackTextViewPadding,
|
feedbackTextViewPadding,
|
||||||
feedbackTextViewPadding,
|
feedbackTextViewPadding,
|
||||||
feedbackTextViewPadding
|
feedbackTextViewPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
background = GradientDrawable().apply {
|
background = GradientDrawable().apply {
|
||||||
@ -108,7 +108,7 @@ class SwipeControlsOverlayLayout(
|
|||||||
icon,
|
icon,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
)
|
)
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ class SwipeControlsOverlayLayout(
|
|||||||
override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) {
|
override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) {
|
||||||
showFeedbackView(
|
showFeedbackView(
|
||||||
"$newVolume",
|
"$newVolume",
|
||||||
if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon
|
if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ class SwipeControlsOverlayLayout(
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
performHapticFeedback(
|
performHapticFeedback(
|
||||||
HapticFeedbackConstants.LONG_PRESS,
|
HapticFeedbackConstants.LONG_PRESS,
|
||||||
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
|
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
package app.revanced.integrations.youtube.videoplayer;
|
package app.revanced.integrations.youtube.videoplayer;
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.youtube.patches.DownloadsPatch;
|
||||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import app.revanced.integrations.shared.StringRef;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ExternalDownloadButton extends BottomControlButton {
|
public class ExternalDownloadButton extends BottomControlButton {
|
||||||
@ -47,39 +44,10 @@ public class ExternalDownloadButton extends BottomControlButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void onDownloadClick(View view) {
|
private static void onDownloadClick(View view) {
|
||||||
Logger.printDebug(() -> "External download button clicked");
|
DownloadsPatch.launchExternalDownloader(
|
||||||
|
VideoInformation.getVideoId(),
|
||||||
final var context = view.getContext();
|
view.getContext(),
|
||||||
// Trim string to avoid any accidental whitespace.
|
true);
|
||||||
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
|
||||||
|
|
||||||
boolean packageEnabled = false;
|
|
||||||
try {
|
|
||||||
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
|
|
||||||
} catch (PackageManager.NameNotFoundException error) {
|
|
||||||
Logger.printDebug(() -> "External downloader could not be found: " + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the package is not installed, show the toast
|
|
||||||
if (!packageEnabled) {
|
|
||||||
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch PowerTube intent
|
|
||||||
try {
|
|
||||||
String content = String.format("https://youtu.be/%s", VideoInformation.getVideoId());
|
|
||||||
|
|
||||||
Intent intent = new Intent("android.intent.action.SEND");
|
|
||||||
intent.setType("text/plain");
|
|
||||||
intent.setPackage(downloaderPackageName);
|
|
||||||
intent.putExtra("android.intent.extra.TEXT", content);
|
|
||||||
context.startActivity(intent);
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Launched the intent with the content: " + content);
|
|
||||||
} catch (Exception error) {
|
|
||||||
Logger.printException(() -> "Failed to launch the intent: " + error, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 1.4.0
|
version = 1.5.0-dev.10
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.2.2"
|
agp = "8.2.2" # 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818
|
||||||
annotation = "1.7.1"
|
annotation = "1.7.1"
|
||||||
kotlin = "1.9.22"
|
kotlin = "1.9.22"
|
||||||
appcompat = "1.7.0-alpha03"
|
appcompat = "1.7.0-alpha03"
|
||||||
|
@ -16,7 +16,7 @@ android {
|
|||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user