mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-05-03 16:14:29 +02:00
chore: Merge branch dev
to main
(#540)
This commit is contained in:
commit
20f633ae66
70
CHANGELOG.md
70
CHANGELOG.md
@ -1,3 +1,73 @@
|
|||||||
|
# [1.1.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.6...v1.1.0-dev.7) (2023-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide emergency box when enabled ([6ca7946](https://github.com/ReVanced/revanced-integrations/commit/6ca7946e8f3d5be76241b88f1d7a5a881629dda9))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Remove viewer discretion dialog` patch ([6f3f882](https://github.com/ReVanced/revanced-integrations/commit/6f3f88264e736b80f88103e795534f86f053c8d1))
|
||||||
|
|
||||||
|
# [1.1.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.5...v1.1.0-dev.6) (2023-12-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - VideoInformation:** Ignore video seek attempts during the last 250ms of video playback ([6263edc](https://github.com/ReVanced/revanced-integrations/commit/6263edce11077f9e9c0629d9260e2f2eaef1c0e8))
|
||||||
|
|
||||||
|
# [1.1.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.4...v1.1.0-dev.5) (2023-12-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide new type of ad ([#545](https://github.com/ReVanced/revanced-integrations/issues/545)) ([e28b8cc](https://github.com/ReVanced/revanced-integrations/commit/e28b8cc59a445ba8f184ba6f5b9e076b7731129a))
|
||||||
|
* **YouTube - Hide ads:** Use correct filter ([4397eed](https://github.com/ReVanced/revanced-integrations/commit/4397eedeeb21edda235cd27cd66088e94bde49e4))
|
||||||
|
|
||||||
|
# [1.1.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.3...v1.1.0-dev.4) (2023-12-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Do not auto skip end segments more than once if using a slow playback speed ([88b3ca4](https://github.com/ReVanced/revanced-integrations/commit/88b3ca4992e8278e1d43dbe5cc7607d4890c0eda))
|
||||||
|
|
||||||
|
# [1.1.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.2...v1.1.0-dev.3) (2023-12-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide fullscreen ads ([0f6dee5](https://github.com/ReVanced/revanced-integrations/commit/0f6dee5bae6b8017a53830587e09079942bc24aa))
|
||||||
|
* **YouTube - Hide layout components:** Hide search result recommendations ([d241e43](https://github.com/ReVanced/revanced-integrations/commit/d241e437ee25cc2211bf06b4a7f8fd1c295fad25))
|
||||||
|
|
||||||
|
# [1.1.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.1...v1.1.0-dev.2) (2023-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Tiktok - Remember clear display:** Use correct name ([2003b91](https://github.com/ReVanced/revanced-integrations/commit/2003b910b12cba445822bfaede7975e00220a81a))
|
||||||
|
|
||||||
|
# [1.1.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.1-dev.2...v1.1.0-dev.1) (2023-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Tiktok:** Add `Remember clear mode` patch ([#544](https://github.com/ReVanced/revanced-integrations/issues/544)) ([05eddb6](https://github.com/ReVanced/revanced-integrations/commit/05eddb68d5d5de1b76545c42313d4e9f9ba6712e))
|
||||||
|
|
||||||
|
## [1.0.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.0.1-dev.1...v1.0.1-dev.2) (2023-12-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Return YouTube Dislike:** Do not prefetch Shorts shelf items on app startup ([697c2aa](https://github.com/ReVanced/revanced-integrations/commit/697c2aaac68976c985f0d838795b03f284e907e8))
|
||||||
|
|
||||||
|
## [1.0.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0...v1.0.1-dev.1) (2023-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Export local statistics with saved settings ([#542](https://github.com/ReVanced/revanced-integrations/issues/542)) ([0ed8e5a](https://github.com/ReVanced/revanced-integrations/commit/0ed8e5a2988c07f3dfbd5dd4e9ef8ed53378fbbe))
|
||||||
|
|
||||||
# [1.0.0](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v1.0.0) (2023-12-12)
|
# [1.0.0](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v1.0.0) (2023-12-12)
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class RemoveViewerDiscretionDialogPatch {
|
||||||
|
public static void confirmDialog(AlertDialog dialog) {
|
||||||
|
if (!SettingsEnum.REMOVE_VIEWER_DISCRETION_DIALOG.getBoolean()) {
|
||||||
|
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
|
||||||
|
dialog.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
button.setSoundEffectsEnabled(false);
|
||||||
|
button.performClick();
|
||||||
|
}
|
||||||
|
}
|
@ -570,7 +570,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean videoIdIsShort = VideoInformation.lastVideoIdIsShort();
|
final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort();
|
||||||
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
||||||
// and the 'is opening/playing' parameter will be false.
|
// and the 'is opening/playing' parameter will be false.
|
||||||
// This hook will be called again when the Short is actually opened.
|
// This hook will be called again when the Short is actually opened.
|
||||||
|
@ -32,6 +32,7 @@ public final class VideoInformation {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static volatile String playerResponseVideoId = "";
|
private static volatile String playerResponseVideoId = "";
|
||||||
|
private static volatile boolean playerResponseVideoIdIsShort;
|
||||||
private static volatile boolean videoIdIsShort;
|
private static volatile boolean videoIdIsShort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,6 +83,7 @@ public final class VideoInformation {
|
|||||||
*/
|
*/
|
||||||
public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) {
|
public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) {
|
||||||
final boolean isShort = playerParametersAreShort(signature);
|
final boolean isShort = playerParametersAreShort(signature);
|
||||||
|
playerResponseVideoIdIsShort = isShort;
|
||||||
if (!isShort || isShortAndOpeningOrPlaying) {
|
if (!isShort || isShortAndOpeningOrPlaying) {
|
||||||
if (videoIdIsShort != isShort) {
|
if (videoIdIsShort != isShort) {
|
||||||
videoIdIsShort = isShort;
|
videoIdIsShort = isShort;
|
||||||
@ -155,20 +157,29 @@ public final class VideoInformation {
|
|||||||
* Caution: If called from a videoTimeHook() callback,
|
* Caution: If called from a videoTimeHook() callback,
|
||||||
* this will cause a recursive call into the same videoTimeHook() callback.
|
* this will cause a recursive call into the same videoTimeHook() callback.
|
||||||
*
|
*
|
||||||
* @param millisecond The millisecond to seek the video to.
|
* @param seekTime The seekTime to seek the video to.
|
||||||
* @return true if the seek was successful.
|
* @return true if the seek was successful.
|
||||||
*/
|
*/
|
||||||
public static boolean seekTo(final long millisecond) {
|
public static boolean seekTo(final long seekTime) {
|
||||||
final long videoLength = getVideoLength();
|
|
||||||
|
|
||||||
// Prevent issues such as play/ pause button or autoplay not working.
|
|
||||||
final long seekToMilliseconds = Math.min(millisecond, VideoInformation.getVideoLength() - 250);
|
|
||||||
|
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "Seeking to " + seekToMilliseconds);
|
final long videoTime = getVideoTime();
|
||||||
|
final long videoLength = getVideoLength();
|
||||||
|
|
||||||
|
// Prevent issues such as play/ pause button or autoplay not working.
|
||||||
|
final long adjustedSeekTime = Math.min(seekTime, videoLength - 250);
|
||||||
|
if (videoTime <= seekTime && videoTime >= adjustedSeekTime) {
|
||||||
|
// Both the current video time and the seekTo are in the last 250ms of the video.
|
||||||
|
// Ignore this seek call, otherwise if a video ends with multiple closely timed segments
|
||||||
|
// then seeking here can create an infinite loop of skip attempts.
|
||||||
|
LogHelper.printDebug(() -> "Ignoring seekTo call as video playback is almost finished. "
|
||||||
|
+ " videoTime: " + videoTime + " videoLength: " + videoLength + " seekTo: " + seekTime);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "Seeking to " + adjustedSeekTime);
|
||||||
//noinspection DataFlowIssue
|
//noinspection DataFlowIssue
|
||||||
return (Boolean) seekMethod.invoke(playerControllerRef.get(), seekToMilliseconds);
|
return (Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to seek", ex);
|
LogHelper.printException(() -> "Failed to seek", ex);
|
||||||
return false;
|
return false;
|
||||||
@ -206,11 +217,17 @@ public final class VideoInformation {
|
|||||||
return playerResponseVideoId;
|
return playerResponseVideoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the last player response video id was a Short.
|
||||||
|
* Includes Shorts shelf items appearing in the feed that are not opened.
|
||||||
|
* @see #lastVideoIdIsShort()
|
||||||
|
*/
|
||||||
|
public static boolean lastPlayerResponseIsShort() {
|
||||||
|
return playerResponseVideoIdIsShort;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the last player response video id _that was opened_ was a Short.
|
* @return If the last player response video id _that was opened_ was a Short.
|
||||||
* <p>
|
|
||||||
* Note: This value returned may not match the status of {@link #getPlayerResponseVideoId()}
|
|
||||||
* since that includes player responses for videos not opened.
|
|
||||||
*/
|
*/
|
||||||
public static boolean lastVideoIdIsShort() {
|
public static boolean lastVideoIdIsShort() {
|
||||||
return videoIdIsShort;
|
return videoIdIsShort;
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
import app.revanced.integrations.utils.StringTrieSearch;
|
import app.revanced.integrations.utils.StringTrieSearch;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public final class AdsFilter extends Filter {
|
public final class AdsFilter extends Filter {
|
||||||
|
// region Fullscreen ad
|
||||||
|
private static long lastTimeClosedFullscreenAd = 0;
|
||||||
|
private static final Instrumentation instrumentation = new Instrumentation();
|
||||||
|
private final StringFilterGroup fullscreenAd;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private final StringFilterGroup shoppingLinks;
|
private final StringFilterGroup shoppingLinks;
|
||||||
|
|
||||||
@ -23,6 +32,22 @@ public final class AdsFilter extends Filter {
|
|||||||
"library_recent_shelf"
|
"library_recent_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Identifiers.
|
||||||
|
|
||||||
|
|
||||||
|
final var carouselAd = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_GENERAL_ADS,
|
||||||
|
"carousel_ad"
|
||||||
|
);
|
||||||
|
addIdentifierCallbacks(carouselAd);
|
||||||
|
|
||||||
|
// Paths.
|
||||||
|
|
||||||
|
fullscreenAd = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_FULLSCREEN_ADS,
|
||||||
|
"_interstitial"
|
||||||
|
);
|
||||||
|
|
||||||
final var buttonedAd = new StringFilterGroup(
|
final var buttonedAd = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_BUTTONED_ADS,
|
SettingsEnum.HIDE_BUTTONED_ADS,
|
||||||
"_buttoned_layout",
|
"_buttoned_layout",
|
||||||
@ -30,7 +55,8 @@ public final class AdsFilter extends Filter {
|
|||||||
"_ad_with",
|
"_ad_with",
|
||||||
"text_image_button_group_layout",
|
"text_image_button_group_layout",
|
||||||
"video_display_button_group_layout",
|
"video_display_button_group_layout",
|
||||||
"landscape_image_wide_button_layout"
|
"landscape_image_wide_button_layout",
|
||||||
|
"video_display_carousel_button_group_layout"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var generalAds = new StringFilterGroup(
|
final var generalAds = new StringFilterGroup(
|
||||||
@ -61,11 +87,6 @@ public final class AdsFilter extends Filter {
|
|||||||
"offer_module_root"
|
"offer_module_root"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var carouselAd = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_GENERAL_ADS,
|
|
||||||
"carousel_ad"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var viewProducts = new StringFilterGroup(
|
final var viewProducts = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_PRODUCTS_BANNER,
|
SettingsEnum.HIDE_PRODUCTS_BANNER,
|
||||||
"product_item",
|
"product_item",
|
||||||
@ -92,30 +113,34 @@ public final class AdsFilter extends Filter {
|
|||||||
"cta_shelf_card"
|
"cta_shelf_card"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
generalAds,
|
generalAds,
|
||||||
buttonedAd,
|
buttonedAd,
|
||||||
merchandise,
|
merchandise,
|
||||||
viewProducts,
|
viewProducts,
|
||||||
selfSponsor,
|
selfSponsor,
|
||||||
|
fullscreenAd,
|
||||||
webLinkPanel,
|
webLinkPanel,
|
||||||
shoppingLinks,
|
shoppingLinks,
|
||||||
movieAds
|
movieAds
|
||||||
);
|
);
|
||||||
this.identifierFilterGroupList.addAll(carouselAd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (exceptions.matches(path))
|
if (exceptions.matches(path))
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check for the index because of likelihood of false positives.
|
|
||||||
if (matchedGroup == shoppingLinks && matchedIndex != 0)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
if (matchedGroup == fullscreenAd && path.contains("|ImageType|")) {
|
||||||
|
closeFullscreenAd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the index because of likelihood of false positives.
|
||||||
|
if (matchedGroup == shoppingLinks && contentIndex != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,4 +151,21 @@ public final class AdsFilter extends Filter {
|
|||||||
public static void hideAdAttributionView(View view) {
|
public static void hideAdAttributionView(View view) {
|
||||||
ReVancedUtils.hideViewBy1dpUnderCondition(SettingsEnum.HIDE_GENERAL_ADS, view);
|
ReVancedUtils.hideViewBy1dpUnderCondition(SettingsEnum.HIDE_GENERAL_ADS, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the fullscreen ad.
|
||||||
|
* <p>
|
||||||
|
* The strategy is to send a back button event to the app to close the fullscreen ad using the back button event.
|
||||||
|
*/
|
||||||
|
private static void closeFullscreenAd() {
|
||||||
|
final var currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Prevent spamming the back button.
|
||||||
|
if (currentTime - lastTimeClosedFullscreenAd < 10000) return;
|
||||||
|
lastTimeClosedFullscreenAd = currentTime;
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "Closing fullscreen ad");
|
||||||
|
|
||||||
|
ReVancedUtils.runOnMainThreadDelayed(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK), 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import androidx.annotation.RequiresApi;
|
|||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
final class ButtonsFilter extends Filter {
|
final class ButtonsFilter extends Filter {
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
||||||
@ -20,14 +21,14 @@ final class ButtonsFilter extends Filter {
|
|||||||
null,
|
null,
|
||||||
VIDEO_ACTION_BAR_PATH
|
VIDEO_ACTION_BAR_PATH
|
||||||
);
|
);
|
||||||
identifierFilterGroupList.addAll(actionBarGroup);
|
addIdentifierCallbacks(actionBarGroup);
|
||||||
|
|
||||||
|
|
||||||
bufferFilterPathGroup = new StringFilterGroup(
|
bufferFilterPathGroup = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
||||||
);
|
);
|
||||||
pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
|
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
"|segmented_like_dislike_button"
|
"|segmented_like_dislike_button"
|
||||||
@ -48,33 +49,33 @@ final class ButtonsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
bufferButtonsGroupList.addAll(
|
bufferButtonsGroupList.addAll(
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_LIVE_CHAT_BUTTON,
|
SettingsEnum.HIDE_LIVE_CHAT_BUTTON,
|
||||||
"yt_outline_message_bubble_overlap"
|
"yt_outline_message_bubble_overlap"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_REPORT_BUTTON,
|
SettingsEnum.HIDE_REPORT_BUTTON,
|
||||||
"yt_outline_flag"
|
"yt_outline_flag"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SHARE_BUTTON,
|
SettingsEnum.HIDE_SHARE_BUTTON,
|
||||||
"yt_outline_share"
|
"yt_outline_share"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_REMIX_BUTTON,
|
SettingsEnum.HIDE_REMIX_BUTTON,
|
||||||
"yt_outline_youtube_shorts_plus"
|
"yt_outline_youtube_shorts_plus"
|
||||||
),
|
),
|
||||||
// Check for clip button both here and using a path filter,
|
// Check for clip button both here and using a path filter,
|
||||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_CLIP_BUTTON,
|
SettingsEnum.HIDE_CLIP_BUTTON,
|
||||||
"yt_outline_scissors"
|
"yt_outline_scissors"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SHOP_BUTTON,
|
SettingsEnum.HIDE_SHOP_BUTTON,
|
||||||
"yt_outline_bag"
|
"yt_outline_bag"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_THANKS_BUTTON,
|
SettingsEnum.HIDE_THANKS_BUTTON,
|
||||||
"yt_outline_dollar_sign_heart"
|
"yt_outline_dollar_sign_heart"
|
||||||
)
|
)
|
||||||
@ -82,7 +83,7 @@ final class ButtonsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEveryFilterGroupEnabled() {
|
private boolean isEveryFilterGroupEnabled() {
|
||||||
for (var group : pathFilterGroupList)
|
for (var group : pathCallbacks)
|
||||||
if (!group.isEnabled()) return false;
|
if (!group.isEnabled()) return false;
|
||||||
|
|
||||||
for (var group : bufferButtonsGroupList)
|
for (var group : bufferButtonsGroupList)
|
||||||
@ -93,7 +94,7 @@ final class ButtonsFilter extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// If the current matched group is the action bar group,
|
// If the current matched group is the action bar group,
|
||||||
// in case every filter group is enabled, hide the action bar.
|
// in case every filter group is enabled, hide the action bar.
|
||||||
if (matchedGroup == actionBarGroup) {
|
if (matchedGroup == actionBarGroup) {
|
||||||
@ -109,6 +110,6 @@ final class ButtonsFilter extends Filter {
|
|||||||
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
|
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package app.revanced.integrations.patches.components;
|
|||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
final class CommentsFilter extends Filter {
|
final class CommentsFilter extends Filter {
|
||||||
|
|
||||||
public CommentsFilter() {
|
public CommentsFilter() {
|
||||||
@ -18,7 +19,7 @@ final class CommentsFilter extends Filter {
|
|||||||
"comments_entry_point_simplebox"
|
"comments_entry_point_simplebox"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
comments,
|
comments,
|
||||||
previewComment
|
previewComment
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
|
|||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.StringTrieSearch;
|
import app.revanced.integrations.utils.StringTrieSearch;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
final class DescriptionComponentsFilter extends Filter {
|
final class DescriptionComponentsFilter extends Filter {
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
@ -48,7 +49,7 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
"transcript_section"
|
"transcript_section"
|
||||||
);
|
);
|
||||||
|
|
||||||
pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
chapterSection,
|
chapterSection,
|
||||||
infoCardsSection,
|
infoCardsSection,
|
||||||
gameSection,
|
gameSection,
|
||||||
@ -61,9 +62,9 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (exceptions.matches(path)) return false;
|
if (exceptions.matches(path)) return false;
|
||||||
|
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
|
||||||
|
|
||||||
final class DummyFilter extends Filter { }
|
|
@ -2,10 +2,11 @@ package app.revanced.integrations.patches.components;
|
|||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public final class HideInfoCardsFilterPatch extends Filter {
|
public final class HideInfoCardsFilterPatch extends Filter {
|
||||||
|
|
||||||
public HideInfoCardsFilterPatch() {
|
public HideInfoCardsFilterPatch() {
|
||||||
identifierFilterGroupList.addAll(
|
addIdentifierCallbacks(
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_INFO_CARDS,
|
SettingsEnum.HIDE_INFO_CARDS,
|
||||||
"info_card_teaser_overlay.eml"
|
"info_card_teaser_overlay.eml"
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.StringTrieSearch;
|
import app.revanced.integrations.utils.StringTrieSearch;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public final class LayoutComponentsFilter extends Filter {
|
public final class LayoutComponentsFilter extends Filter {
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
|
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
|
||||||
private static final ByteArrayAsStringFilterGroup mixPlaylistsExceptions2 = new ByteArrayAsStringFilterGroup(
|
private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup(
|
||||||
null,
|
null,
|
||||||
"cell_description_body"
|
"cell_description_body"
|
||||||
);
|
);
|
||||||
private final CustomFilterGroup custom;
|
private final CustomFilterGroup custom;
|
||||||
|
|
||||||
private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
|
private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_MIX_PLAYLISTS,
|
SettingsEnum.HIDE_MIX_PLAYLISTS,
|
||||||
"&list="
|
"&list="
|
||||||
);
|
);
|
||||||
@ -26,6 +28,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
private final StringFilterGroup inFeedSurvey;
|
private final StringFilterGroup inFeedSurvey;
|
||||||
private final StringFilterGroup notifyMe;
|
private final StringFilterGroup notifyMe;
|
||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
|
private final ByteArrayFilterGroup searchResultRecommendations;
|
||||||
|
private final StringFilterGroup searchResultVideo;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
mixPlaylistsExceptions.addPatterns(
|
mixPlaylistsExceptions.addPatterns(
|
||||||
@ -39,11 +43,31 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
"home_video_with_context",
|
"home_video_with_context",
|
||||||
"related_video_with_context",
|
"related_video_with_context",
|
||||||
|
"search_video_with_context",
|
||||||
"comment_thread", // Whitelist comments
|
"comment_thread", // Whitelist comments
|
||||||
"|comment.", // Whitelist comment replies
|
"|comment.", // Whitelist comment replies
|
||||||
"library_recent_shelf"
|
"library_recent_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Identifiers.
|
||||||
|
|
||||||
|
final var graySeparator = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_GRAY_SEPARATOR,
|
||||||
|
"cell_divider" // layout residue (gray line above the buttoned ad),
|
||||||
|
);
|
||||||
|
|
||||||
|
final var chipsShelf = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CHIPS_SHELF,
|
||||||
|
"chips_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
|
addIdentifierCallbacks(
|
||||||
|
graySeparator,
|
||||||
|
chipsShelf
|
||||||
|
);
|
||||||
|
|
||||||
|
// Paths.
|
||||||
|
|
||||||
custom = new CustomFilterGroup(
|
custom = new CustomFilterGroup(
|
||||||
SettingsEnum.CUSTOM_FILTER,
|
SettingsEnum.CUSTOM_FILTER,
|
||||||
SettingsEnum.CUSTOM_FILTER_STRINGS
|
SettingsEnum.CUSTOM_FILTER_STRINGS
|
||||||
@ -64,7 +88,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"sponsorships_comments_upsell"
|
"sponsorships_comments_upsell"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
final var channelMemberShelf = new StringFilterGroup(
|
final var channelMemberShelf = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_CHANNEL_MEMBER_SHELF,
|
SettingsEnum.HIDE_CHANNEL_MEMBER_SHELF,
|
||||||
"member_recognition_shelf"
|
"member_recognition_shelf"
|
||||||
@ -107,6 +130,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"channel_guidelines_entry_banner"
|
"channel_guidelines_entry_banner"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var emergencyBox = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_EMERGENCY_BOX,
|
||||||
|
"emergency_onebox"
|
||||||
|
);
|
||||||
|
|
||||||
// The player audio track button does the exact same function as the audio track flyout menu option.
|
// The player audio track button does the exact same function as the audio track flyout menu option.
|
||||||
// But if the copy url button is shown, these button clashes and the the audio button does not work.
|
// But if the copy url button is shown, these button clashes and the the audio button does not work.
|
||||||
// Previously this was a setting to show/hide the player button.
|
// Previously this was a setting to show/hide the player button.
|
||||||
@ -155,10 +183,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"image_shelf"
|
"image_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var graySeparator = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_GRAY_SEPARATOR,
|
|
||||||
"cell_divider" // layout residue (gray line above the buttoned ad),
|
|
||||||
);
|
|
||||||
|
|
||||||
final var timedReactions = new StringFilterGroup(
|
final var timedReactions = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_TIMED_REACTIONS,
|
SettingsEnum.HIDE_TIMED_REACTIONS,
|
||||||
@ -181,11 +205,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"compact_sponsor_button"
|
"compact_sponsor_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var chipsShelf = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_CHIPS_SHELF,
|
|
||||||
"chips_shelf"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var channelWatermark = new StringFilterGroup(
|
final var channelWatermark = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK,
|
SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK,
|
||||||
"featured_channel_watermark_overlay"
|
"featured_channel_watermark_overlay"
|
||||||
@ -196,23 +215,36 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"mixed_content_shelf"
|
"mixed_content_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
searchResultVideo = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
|
||||||
|
"search_video_with_context.eml"
|
||||||
|
);
|
||||||
|
|
||||||
|
searchResultRecommendations = new ByteArrayFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
|
||||||
|
"endorsement_header_footer"
|
||||||
|
);
|
||||||
|
|
||||||
|
addPathCallbacks(
|
||||||
|
custom,
|
||||||
|
expandableMetadata,
|
||||||
|
inFeedSurvey,
|
||||||
|
notifyMe,
|
||||||
channelBar,
|
channelBar,
|
||||||
communityPosts,
|
communityPosts,
|
||||||
paidContent,
|
paidContent,
|
||||||
|
searchResultVideo,
|
||||||
latestPosts,
|
latestPosts,
|
||||||
channelWatermark,
|
channelWatermark,
|
||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
quickActions,
|
quickActions,
|
||||||
expandableMetadata,
|
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
compactBanner,
|
compactBanner,
|
||||||
inFeedSurvey,
|
|
||||||
joinMembership,
|
joinMembership,
|
||||||
medicalPanel,
|
medicalPanel,
|
||||||
notifyMe,
|
|
||||||
videoQualityMenuFooter,
|
videoQualityMenuFooter,
|
||||||
infoPanel,
|
infoPanel,
|
||||||
|
emergencyBox,
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
channelGuidelines,
|
channelGuidelines,
|
||||||
audioTrackButton,
|
audioTrackButton,
|
||||||
@ -220,32 +252,31 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
timedReactions,
|
timedReactions,
|
||||||
imageShelf,
|
imageShelf,
|
||||||
channelMemberShelf,
|
channelMemberShelf,
|
||||||
forYouShelf,
|
forYouShelf
|
||||||
custom
|
|
||||||
);
|
|
||||||
|
|
||||||
this.identifierFilterGroupList.addAll(
|
|
||||||
graySeparator,
|
|
||||||
chipsShelf
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (matchedGroup == searchResultVideo) {
|
||||||
|
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The groups are excluded from the filter due to the exceptions list below.
|
// The groups are excluded from the filter due to the exceptions list below.
|
||||||
// Filter them separately here.
|
// Filter them separately here.
|
||||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
|
||||||
if (matchedGroup != custom && exceptions.matches(path))
|
if (matchedGroup != custom && exceptions.matches(path))
|
||||||
return false; // Exceptions are not filtered.
|
return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
// TODO: This also hides the feed Shorts shelf header
|
// TODO: This also hides the feed Shorts shelf header
|
||||||
if (matchedGroup == searchResultShelfHeader && matchedIndex != 0) return false;
|
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +195,14 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||||||
super(setting, filters);
|
super(setting, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
public ByteArrayFilterGroup(SettingsEnum setting, String... filters) {
|
||||||
|
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void buildFailurePatterns() {
|
private synchronized void buildFailurePatterns() {
|
||||||
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
||||||
LogHelper.printDebug(() -> "Building failure array for: " + this);
|
LogHelper.printDebug(() -> "Building failure array for: " + this);
|
||||||
@ -211,12 +219,14 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||||||
int matchedLength = 0;
|
int matchedLength = 0;
|
||||||
int matchedIndex = -1;
|
int matchedIndex = -1;
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
if (failurePatterns == null) {
|
int[][] failures = failurePatterns;
|
||||||
|
if (failures == null) {
|
||||||
buildFailurePatterns(); // Lazy load.
|
buildFailurePatterns(); // Lazy load.
|
||||||
|
failures = failurePatterns;
|
||||||
}
|
}
|
||||||
for (int i = 0, length = filters.length; i < length; i++) {
|
for (int i = 0, length = filters.length; i < length; i++) {
|
||||||
byte[] filter = filters[i];
|
byte[] filter = filters[i];
|
||||||
matchedIndex = indexOf(bytes, filter, failurePatterns[i]);
|
matchedIndex = indexOf(bytes, filter, failures[i]);
|
||||||
if (matchedIndex >= 0) {
|
if (matchedIndex >= 0) {
|
||||||
matchedLength = filter.length;
|
matchedLength = filter.length;
|
||||||
break;
|
break;
|
||||||
@ -228,34 +238,16 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) {
|
|
||||||
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||||
|
|
||||||
private final List<T> filterGroups = new ArrayList<>();
|
private final List<T> filterGroups = new ArrayList<>();
|
||||||
/**
|
private final TrieSearch<V> search = createSearchGraph();
|
||||||
* Search graph. Created only if needed.
|
|
||||||
*/
|
|
||||||
private volatile TrieSearch<V> search;
|
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
protected final void addAll(final T... groups) {
|
protected final void addAll(final T... groups) {
|
||||||
filterGroups.addAll(Arrays.asList(groups));
|
filterGroups.addAll(Arrays.asList(groups));
|
||||||
search = null; // Rebuild, if already created.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final synchronized void buildSearch() {
|
for (T group : groups) {
|
||||||
// Since litho filtering is multi-threaded, this method can be concurrently called by multiple threads.
|
|
||||||
if (search != null) return; // Thread race and another thread already initialized the search.
|
|
||||||
LogHelper.printDebug(() -> "Creating prefix search tree for: " + this);
|
|
||||||
TrieSearch<V> search = createSearchGraph();
|
|
||||||
for (T group : filterGroups) {
|
|
||||||
if (!group.includeInSearch()) {
|
if (!group.includeInSearch()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -270,7 +262,6 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.search = search; // Must set after it's completely initialized.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -293,9 +284,6 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected FilterGroup.FilterGroupResult check(V stack) {
|
protected FilterGroup.FilterGroupResult check(V stack) {
|
||||||
if (search == null) {
|
|
||||||
buildSearch(); // Lazy load.
|
|
||||||
}
|
|
||||||
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
||||||
search.matches(stack, result);
|
search.matches(stack, result);
|
||||||
return result;
|
return result;
|
||||||
@ -322,42 +310,90 @@ final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters litho based components.
|
||||||
|
*
|
||||||
|
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
||||||
|
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||||
|
*
|
||||||
|
* To filter {@link FilterContentType#PROTOBUFFER}, first add a callback to
|
||||||
|
* either an identifier or a path.
|
||||||
|
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
|
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
||||||
|
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||||
|
*
|
||||||
|
* All callbacks must be registered before the constructor completes.
|
||||||
|
*/
|
||||||
abstract class Filter {
|
abstract class Filter {
|
||||||
/**
|
|
||||||
* All group filters must be set before the constructor call completes.
|
|
||||||
* Otherwise {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}
|
|
||||||
* will never be called for any matches.
|
|
||||||
*/
|
|
||||||
|
|
||||||
protected final StringFilterGroupList pathFilterGroupList = new StringFilterGroupList();
|
public enum FilterContentType {
|
||||||
protected final StringFilterGroupList identifierFilterGroupList = new StringFilterGroupList();
|
IDENTIFIER,
|
||||||
|
PATH,
|
||||||
|
PROTOBUFFER
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier callbacks. Do not add to this instance,
|
||||||
|
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
|
||||||
|
*/
|
||||||
|
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Path callbacks. Do not add to this instance,
|
||||||
|
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||||
|
*/
|
||||||
|
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
|
* if any of the groups are found.
|
||||||
|
*/
|
||||||
|
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
||||||
|
identifierCallbacks.addAll(Arrays.asList(groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
|
* if any of the groups are found.
|
||||||
|
*/
|
||||||
|
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
||||||
|
pathCallbacks.addAll(Arrays.asList(groups));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after an enabled filter has been matched.
|
* Called after an enabled filter has been matched.
|
||||||
* Default implementation is to always filter the matched item.
|
* Default implementation is to always filter the matched component and log the action.
|
||||||
* Subclasses can perform additional or different checks if needed.
|
* Subclasses can perform additional or different checks if needed.
|
||||||
* <p>
|
* <p>
|
||||||
|
* If the content is to be filtered, subclasses should always
|
||||||
|
* call this method (and never return a plain 'true').
|
||||||
|
* That way the logs will always show when a component was filtered and which filter hide it.
|
||||||
|
* <p>
|
||||||
* Method is called off the main thread.
|
* Method is called off the main thread.
|
||||||
*
|
*
|
||||||
* @param matchedList The list the group filter belongs to.
|
|
||||||
* @param matchedGroup The actual filter that matched.
|
* @param matchedGroup The actual filter that matched.
|
||||||
* @param matchedIndex Matched index of string/array.
|
* @param contentType The type of content matched.
|
||||||
* @return True if the litho item should be filtered out.
|
* @param contentIndex Matched index of the identifier or path.
|
||||||
|
* @return True if the litho component should be filtered out.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||||
if (matchedList == identifierFilterGroupList) {
|
String filterSimpleName = getClass().getSimpleName();
|
||||||
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
|
if (contentType == FilterContentType.IDENTIFIER) {
|
||||||
|
LogHelper.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
|
LogHelper.printDebug(() -> filterSimpleName + " Filtered path: " + path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for actual filters.
|
||||||
|
*/
|
||||||
|
final class DummyFilter extends Filter { }
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LithoFilterPatch {
|
public final class LithoFilterPatch {
|
||||||
@ -437,8 +473,10 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList);
|
filterUsingCallbacks(identifierSearchTree, filter,
|
||||||
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
|
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
||||||
|
filterUsingCallbacks(pathSearchTree, filter,
|
||||||
|
filter.pathCallbacks, Filter.FilterContentType.PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Using: "
|
LogHelper.printDebug(() -> "Using: "
|
||||||
@ -448,18 +486,19 @@ public final class LithoFilterPatch {
|
|||||||
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
|
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
|
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
|
||||||
Filter filter, FilterGroupList<T, ? extends FilterGroup<T>> list) {
|
Filter filter, List<StringFilterGroup> groups,
|
||||||
for (FilterGroup<T> group : list) {
|
Filter.FilterContentType type) {
|
||||||
|
for (StringFilterGroup group : groups) {
|
||||||
if (!group.includeInSearch()) {
|
if (!group.includeInSearch()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (T pattern : group.filters) {
|
for (String pattern : group.filters) {
|
||||||
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
|
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
|
||||||
if (!group.isEnabled()) return false;
|
if (!group.isEnabled()) return false;
|
||||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
|
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
|
||||||
list, group, matchedStartIndex);
|
group, type, matchedStartIndex);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
|||||||
public static volatile boolean isPlaybackSpeedMenuVisible;
|
public static volatile boolean isPlaybackSpeedMenuVisible;
|
||||||
|
|
||||||
public PlaybackSpeedMenuFilterPatch() {
|
public PlaybackSpeedMenuFilterPatch() {
|
||||||
pathFilterGroupList.addAll(new StringFilterGroup(
|
addPathCallbacks(new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"playback_speed_sheet_content.eml-js"
|
"playback_speed_sheet_content.eml-js"
|
||||||
));
|
));
|
||||||
@ -20,7 +20,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
isPlaybackSpeedMenuVisible = true;
|
isPlaybackSpeedMenuVisible = true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -8,65 +8,64 @@ import androidx.annotation.RequiresApi;
|
|||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||||
|
|
||||||
// Search the buffer only if the flyout menu path is found.
|
|
||||||
// Handle the searching in this class instead of adding to the global filter group (which searches all the time)
|
|
||||||
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
private final ByteArrayFilterGroup exception;
|
private final ByteArrayFilterGroup exception;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public PlayerFlyoutMenuItemsFilter() {
|
public PlayerFlyoutMenuItemsFilter() {
|
||||||
exception = new ByteArrayAsStringFilterGroup(
|
exception = new ByteArrayFilterGroup(
|
||||||
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
|
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
|
||||||
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
||||||
"quality_sheet"
|
"quality_sheet"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Using pathFilterGroupList due to new flyout panel(A/B)
|
// Using pathFilterGroupList due to new flyout panel(A/B)
|
||||||
pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
new StringFilterGroup(null, "overflow_menu_item.eml|")
|
new StringFilterGroup(null, "overflow_menu_item.eml|")
|
||||||
);
|
);
|
||||||
|
|
||||||
flyoutFilterGroupList.addAll(
|
flyoutFilterGroupList.addAll(
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_CAPTIONS_MENU,
|
SettingsEnum.HIDE_CAPTIONS_MENU,
|
||||||
"closed_caption"
|
"closed_caption"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
||||||
"yt_outline_gear"
|
"yt_outline_gear"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
|
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
|
||||||
"yt_outline_arrow_repeat_1_"
|
"yt_outline_arrow_repeat_1_"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_AMBIENT_MODE_MENU,
|
SettingsEnum.HIDE_AMBIENT_MODE_MENU,
|
||||||
"yt_outline_screen_light"
|
"yt_outline_screen_light"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_REPORT_MENU,
|
SettingsEnum.HIDE_REPORT_MENU,
|
||||||
"yt_outline_flag"
|
"yt_outline_flag"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_HELP_MENU,
|
SettingsEnum.HIDE_HELP_MENU,
|
||||||
"yt_outline_question_circle"
|
"yt_outline_question_circle"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_MORE_INFO_MENU,
|
SettingsEnum.HIDE_MORE_INFO_MENU,
|
||||||
"yt_outline_info_circle"
|
"yt_outline_info_circle"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SPEED_MENU,
|
SettingsEnum.HIDE_SPEED_MENU,
|
||||||
"yt_outline_play_arrow_half_circle"
|
"yt_outline_play_arrow_half_circle"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_AUDIO_TRACK_MENU,
|
SettingsEnum.HIDE_AUDIO_TRACK_MENU,
|
||||||
"yt_outline_person_radar"
|
"yt_outline_person_radar"
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_WATCH_IN_VR_MENU,
|
SettingsEnum.HIDE_WATCH_IN_VR_MENU,
|
||||||
"yt_outline_vr"
|
"yt_outline_vr"
|
||||||
)
|
)
|
||||||
@ -75,15 +74,15 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// Shorts also use this player flyout panel
|
// Shorts also use this player flyout panel
|
||||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered())
|
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Only 1 group is added to the parent class, so the matched group must be the overflow menu.
|
// Only 1 path callback was added, so the matched group must be the overflow menu.
|
||||||
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
if (contentIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -71,21 +71,21 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||||||
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
public ReturnYouTubeDislikeFilterPatch() {
|
public ReturnYouTubeDislikeFilterPatch() {
|
||||||
pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
new StringFilterGroup(SettingsEnum.RYD_SHORTS, "|shorts_dislike_button.eml|")
|
new StringFilterGroup(SettingsEnum.RYD_SHORTS, "|shorts_dislike_button.eml|")
|
||||||
);
|
);
|
||||||
// After the dislikes icon name is some binary data and then the video id for that specific short.
|
// After the dislikes icon name is some binary data and then the video id for that specific short.
|
||||||
videoIdFilterGroup.addAll(
|
videoIdFilterGroup.addAll(
|
||||||
// Video was previously disliked before video was opened.
|
// Video was previously disliked before video was opened.
|
||||||
new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_on_shadowed"),
|
new ByteArrayFilterGroup(null, "ic_right_dislike_on_shadowed"),
|
||||||
// Video was not already disliked.
|
// Video was not already disliked.
|
||||||
new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_off_shadowed")
|
new ByteArrayFilterGroup(null, "ic_right_dislike_off_shadowed")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
|
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
|
||||||
if (result.isFiltered()) {
|
if (result.isFiltered()) {
|
||||||
String matchedVideoId = findVideoId(protobufBufferArray);
|
String matchedVideoId = findVideoId(protobufBufferArray);
|
||||||
@ -112,7 +112,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This could use {@link TrieSearch}, but since the video ids are constantly changing
|
* This could use {@link TrieSearch}, but since the patterns are constantly changing
|
||||||
* the overhead of updating the Trie might negate the search performance gain.
|
* the overhead of updating the Trie might negate the search performance gain.
|
||||||
*/
|
*/
|
||||||
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
|
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
|
||||||
|
@ -10,7 +10,7 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
|||||||
import static app.revanced.integrations.utils.ReVancedUtils.hideViewBy1dpUnderCondition;
|
import static app.revanced.integrations.utils.ReVancedUtils.hideViewBy1dpUnderCondition;
|
||||||
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
|
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
|
||||||
|
|
||||||
/** @noinspection unused*/
|
@SuppressWarnings("unused")
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public final class ShortsFilter extends Filter {
|
public final class ShortsFilter extends Filter {
|
||||||
public static PivotBar pivotBar; // Set by patch.
|
public static PivotBar pivotBar; // Set by patch.
|
||||||
@ -49,7 +49,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
"suggested_action"
|
"suggested_action"
|
||||||
);
|
);
|
||||||
|
|
||||||
identifierFilterGroupList.addAll(shorts, shelfHeader, thanksButton);
|
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
||||||
|
|
||||||
// Shorts player components.
|
// Shorts player components.
|
||||||
var joinButton = new StringFilterGroup(
|
var joinButton = new StringFilterGroup(
|
||||||
@ -87,22 +87,22 @@ public final class ShortsFilter extends Filter {
|
|||||||
"ContainerType|shorts_video_action_button"
|
"ContainerType|shorts_video_action_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
pathFilterGroupList.addAll(
|
addPathCallbacks(
|
||||||
joinButton, subscribeButton, subscribeButtonPaused,
|
joinButton, subscribeButton, subscribeButtonPaused,
|
||||||
channelBar, soundButton, infoPanel, videoActionButton
|
channelBar, soundButton, infoPanel, videoActionButton
|
||||||
);
|
);
|
||||||
|
|
||||||
var shortsCommentButton = new ByteArrayAsStringFilterGroup(
|
var shortsCommentButton = new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON,
|
SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||||
"reel_comment_button"
|
"reel_comment_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
var shortsShareButton = new ByteArrayAsStringFilterGroup(
|
var shortsShareButton = new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS_SHARE_BUTTON,
|
SettingsEnum.HIDE_SHORTS_SHARE_BUTTON,
|
||||||
"reel_share_button"
|
"reel_share_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
var shortsRemixButton = new ByteArrayAsStringFilterGroup(
|
var shortsRemixButton = new ByteArrayFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS_REMIX_BUTTON,
|
SettingsEnum.HIDE_SHORTS_REMIX_BUTTON,
|
||||||
"reel_remix_button"
|
"reel_remix_button"
|
||||||
);
|
);
|
||||||
@ -112,19 +112,19 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedList == pathFilterGroupList) {
|
if (contentType == FilterContentType.PATH) {
|
||||||
// Always filter if matched.
|
// Always filter if matched.
|
||||||
if (matchedGroup == soundButton ||
|
if (matchedGroup == soundButton ||
|
||||||
matchedGroup == infoPanel ||
|
matchedGroup == infoPanel ||
|
||||||
matchedGroup == channelBar ||
|
matchedGroup == channelBar ||
|
||||||
matchedGroup == subscribeButtonPaused
|
matchedGroup == subscribeButtonPaused
|
||||||
) return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
|
||||||
// Video action buttons (comment, share, remix) have the same path.
|
// Video action buttons (comment, share, remix) have the same path.
|
||||||
if (matchedGroup == videoActionButton) {
|
if (matchedGroup == videoActionButton) {
|
||||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
||||||
identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -133,18 +133,18 @@ public final class ShortsFilter extends Filter {
|
|||||||
// to avoid false positives.
|
// to avoid false positives.
|
||||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH))
|
if (path.startsWith(REEL_CHANNEL_BAR_PATH))
|
||||||
if (matchedGroup == subscribeButton) return super.isFiltered(
|
if (matchedGroup == subscribeButton) return super.isFiltered(
|
||||||
identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if (matchedGroup == shelfHeader) {
|
} else if (matchedGroup == shelfHeader) {
|
||||||
// Because the header is used in watch history and possibly other places, check for the index,
|
// 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.
|
// which is 0 when the shelf header is used for Shorts.
|
||||||
if (matchedIndex != 0) return false;
|
if (contentIndex != 0) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
|
@ -9,11 +9,12 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
|
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
|
||||||
*/
|
*/
|
||||||
public final class VideoQualityMenuFilterPatch extends Filter {
|
public final class VideoQualityMenuFilterPatch extends Filter {
|
||||||
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
|
// Must be volatile or synchronized, as litho filtering runs off main thread
|
||||||
|
// and this field is then access from the main thread.
|
||||||
public static volatile boolean isVideoQualityMenuVisible;
|
public static volatile boolean isVideoQualityMenuVisible;
|
||||||
|
|
||||||
public VideoQualityMenuFilterPatch() {
|
public VideoQualityMenuFilterPatch() {
|
||||||
pathFilterGroupList.addAll(new StringFilterGroup(
|
addPathCallbacks(new StringFilterGroup(
|
||||||
SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU,
|
SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU,
|
||||||
"quick_quality_sheet_content.eml-js"
|
"quick_quality_sheet_content.eml-js"
|
||||||
));
|
));
|
||||||
@ -21,7 +22,7 @@ public final class VideoQualityMenuFilterPatch extends Filter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
isVideoQualityMenuVisible = true;
|
isVideoQualityMenuVisible = true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -43,6 +43,7 @@ public enum SettingsEnum {
|
|||||||
"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),
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
|
HIDE_FULLSCREEN_ADS("revanced_hide_fullscreen_ads", BOOLEAN, TRUE),
|
||||||
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
|
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
|
||||||
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
|
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
|
||||||
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
|
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
|
||||||
@ -121,6 +122,7 @@ public enum SettingsEnum {
|
|||||||
HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE),
|
HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE),
|
||||||
HIDE_FOR_YOU_SHELF("revanced_hide_for_you_shelf", BOOLEAN, TRUE),
|
HIDE_FOR_YOU_SHELF("revanced_hide_for_you_shelf", BOOLEAN, TRUE),
|
||||||
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
|
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
|
||||||
|
HIDE_SEARCH_RESULT_RECOMMENDATIONS("revanced_hide_search_result_recommendations", BOOLEAN, TRUE),
|
||||||
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
|
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
|
||||||
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
||||||
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
||||||
@ -207,6 +209,8 @@ public enum SettingsEnum {
|
|||||||
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
|
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
|
||||||
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
|
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
|
||||||
REMOVE_TRACKING_QUERY_PARAMETER("revanced_remove_tracking_query_parameter", BOOLEAN, TRUE),
|
REMOVE_TRACKING_QUERY_PARAMETER("revanced_remove_tracking_query_parameter", BOOLEAN, TRUE),
|
||||||
|
REMOVE_VIEWER_DISCRETION_DIALOG("revanced_remove_viewer_discretion_dialog", BOOLEAN, FALSE,
|
||||||
|
"revanced_remove_viewer_discretion_dialog_user_dialog_message"),
|
||||||
|
|
||||||
// Swipe controls
|
// Swipe controls
|
||||||
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
||||||
@ -597,8 +601,6 @@ public enum SettingsEnum {
|
|||||||
case SB_LAST_VIP_CHECK:
|
case SB_LAST_VIP_CHECK:
|
||||||
case SB_HIDE_EXPORT_WARNING:
|
case SB_HIDE_EXPORT_WARNING:
|
||||||
case SB_SEEN_GUIDELINES:
|
case SB_SEEN_GUIDELINES:
|
||||||
case SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS:
|
|
||||||
case SB_LOCAL_TIME_SAVED_MILLISECONDS:
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -26,8 +26,6 @@ import android.widget.EditText;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.settings.SharedPrefCategory;
|
import app.revanced.integrations.settings.SharedPrefCategory;
|
||||||
import app.revanced.integrations.sponsorblock.SegmentPlaybackController;
|
import app.revanced.integrations.sponsorblock.SegmentPlaybackController;
|
||||||
@ -471,8 +469,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final DecimalFormat statsNumberOfSegmentsSkippedFormatter = new DecimalFormat("#,###,###");
|
|
||||||
|
|
||||||
private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) {
|
private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) {
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
try {
|
try {
|
||||||
@ -514,7 +510,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
// number of segment submissions (does not include ignored segments)
|
// number of segment submissions (does not include ignored segments)
|
||||||
Preference preference = new Preference(context);
|
Preference preference = new Preference(context);
|
||||||
statsCategory.addPreference(preference);
|
statsCategory.addPreference(preference);
|
||||||
String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount);
|
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
|
||||||
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
|
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
|
||||||
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||||
preference.setSelectable(false);
|
preference.setSelectable(false);
|
||||||
@ -550,7 +546,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
stats_saved = str("sb_stats_saved_zero");
|
stats_saved = str("sb_stats_saved_zero");
|
||||||
stats_saved_sum = str("sb_stats_saved_sum_zero");
|
stats_saved_sum = str("sb_stats_saved_sum_zero");
|
||||||
} else {
|
} else {
|
||||||
stats_saved = str("sb_stats_saved", statsNumberOfSegmentsSkippedFormatter.format(stats.viewCount));
|
stats_saved = str("sb_stats_saved",
|
||||||
|
SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount));
|
||||||
stats_saved_sum = str("sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
|
stats_saved_sum = str("sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
|
||||||
}
|
}
|
||||||
preference.setTitle(fromHtml(stats_saved));
|
preference.setTitle(fromHtml(stats_saved));
|
||||||
@ -573,7 +570,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
statsCategory.addPreference(preference);
|
statsCategory.addPreference(preference);
|
||||||
|
|
||||||
Runnable updateStatsSelfSaved = () -> {
|
Runnable updateStatsSelfSaved = () -> {
|
||||||
String formatted = statsNumberOfSegmentsSkippedFormatter.format(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
|
String formatted = SponsorBlockUtils.getNumberOfSkipsString(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
|
||||||
preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted)));
|
preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted)));
|
||||||
String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / 1000);
|
String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / 1000);
|
||||||
preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved)));
|
preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved)));
|
||||||
|
@ -510,15 +510,18 @@ public class SegmentPlaybackController {
|
|||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
|
|
||||||
// If trying to seek to end of the video, YouTube can seek just before of the actual end.
|
|
||||||
// (especially if the video does not end on a whole second boundary).
|
|
||||||
// This causes additional segment skip attempts, even though it cannot seek any closer to the desired time.
|
|
||||||
// Check for and ignore repeated skip attempts of the same segment over a small time period.
|
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
final long minimumMillisecondsBetweenSkippingSameSegment = 500;
|
if (lastSegmentSkipped == segmentToSkip) {
|
||||||
if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) {
|
// If trying to seek to end of the video, YouTube can seek just before of the actual end.
|
||||||
LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip);
|
// (especially if the video does not end on a whole second boundary).
|
||||||
return;
|
// This causes additional segment skip attempts, even though it cannot seek any closer to the desired time.
|
||||||
|
// Check for and ignore repeated skip attempts of the same segment over a small time period.
|
||||||
|
final long minTimeBetweenSkippingSameSegment = Math.max(500,
|
||||||
|
(long) (500 / VideoInformation.getPlaybackSpeed()));
|
||||||
|
if (now - lastSegmentSkippedTime < minTimeBetweenSkippingSameSegment) {
|
||||||
|
LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Skipping segment: " + segmentToSkip);
|
LogHelper.printDebug(() -> "Skipping segment: " + segmentToSkip);
|
||||||
|
@ -12,6 +12,7 @@ import android.widget.EditText;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.text.NumberFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@ -39,6 +40,7 @@ public class SponsorBlockUtils {
|
|||||||
private static final SimpleDateFormat manualEditTimeFormatter = new SimpleDateFormat(MANUAL_EDIT_TIME_FORMAT);
|
private static final SimpleDateFormat manualEditTimeFormatter = new SimpleDateFormat(MANUAL_EDIT_TIME_FORMAT);
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
private static final SimpleDateFormat voteSegmentTimeFormatter = new SimpleDateFormat();
|
private static final SimpleDateFormat voteSegmentTimeFormatter = new SimpleDateFormat();
|
||||||
|
private static final NumberFormat statsNumberFormatter = NumberFormat.getNumberInstance();
|
||||||
static {
|
static {
|
||||||
TimeZone utc = TimeZone.getTimeZone("UTC");
|
TimeZone utc = TimeZone.getTimeZone("UTC");
|
||||||
manualEditTimeFormatter.setTimeZone(utc);
|
manualEditTimeFormatter.setTimeZone(utc);
|
||||||
@ -402,19 +404,27 @@ public class SponsorBlockUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getNumberOfSkipsString(int viewCount) {
|
||||||
|
return statsNumberFormatter.format(viewCount);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getTimeSavedString(long totalSecondsSaved) {
|
public static String getTimeSavedString(long totalSecondsSaved) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||||
final long hoursSaved = duration.toHours();
|
final long hours = duration.toHours();
|
||||||
final long minutesSaved = duration.toMinutes() % 60;
|
final long minutes = duration.toMinutes() % 60;
|
||||||
if (hoursSaved > 0) {
|
// Format all numbers so non-western numbers use a consistent appearance.
|
||||||
return str("sb_stats_saved_hour_format", hoursSaved, minutesSaved);
|
String minutesFormatted = statsNumberFormatter.format(minutes);
|
||||||
|
if (hours > 0) {
|
||||||
|
String hoursFormatted = statsNumberFormatter.format(hours);
|
||||||
|
return str("sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
||||||
}
|
}
|
||||||
final long secondsSaved = duration.getSeconds() % 60;
|
final long seconds = duration.getSeconds() % 60;
|
||||||
if (minutesSaved > 0) {
|
String secondsFormatted = statsNumberFormatter.format(seconds);
|
||||||
return str("sb_stats_saved_minute_format", minutesSaved, secondsSaved);
|
if (minutes > 0) {
|
||||||
|
return str("sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
||||||
}
|
}
|
||||||
return str("sb_stats_saved_second_format", secondsSaved);
|
return str("sb_stats_saved_second_format", secondsFormatted);
|
||||||
}
|
}
|
||||||
return "error"; // will never be reached. YouTube requires Android O or greater
|
return "error"; // will never be reached. YouTube requires Android O or greater
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.tiktok.cleardisplay;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class RememberClearDisplayPatch {
|
||||||
|
public static boolean getClearDisplayState() {
|
||||||
|
return SettingsEnum.CLEAR_DISPLAY.getBoolean();
|
||||||
|
}
|
||||||
|
public static void rememberClearDisplayState(boolean newState) {
|
||||||
|
SettingsEnum.CLEAR_DISPLAY.saveValue(newState);
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ public enum SettingsEnum {
|
|||||||
MIN_MAX_LIKES("min_max_likes", STRING, "0-" + Long.MAX_VALUE, true),
|
MIN_MAX_LIKES("min_max_likes", STRING, "0-" + Long.MAX_VALUE, true),
|
||||||
DOWNLOAD_PATH("down_path", STRING, "DCIM/TikTok"),
|
DOWNLOAD_PATH("down_path", STRING, "DCIM/TikTok"),
|
||||||
DOWNLOAD_WATERMARK("down_watermark", BOOLEAN, TRUE),
|
DOWNLOAD_WATERMARK("down_watermark", BOOLEAN, TRUE),
|
||||||
|
CLEAR_DISPLAY("clear_display", BOOLEAN, FALSE),
|
||||||
SIM_SPOOF("simspoof", BOOLEAN, TRUE, true),
|
SIM_SPOOF("simspoof", BOOLEAN, TRUE, true),
|
||||||
SIM_SPOOF_ISO("simspoof_iso", STRING, "us"),
|
SIM_SPOOF_ISO("simspoof_iso", STRING, "us"),
|
||||||
SIMSPOOF_MCCMNC("simspoof_mccmnc", STRING, "310160"),
|
SIMSPOOF_MCCMNC("simspoof_mccmnc", STRING, "310160"),
|
||||||
|
@ -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.0.0
|
version = 1.1.0-dev.7
|
||||||
|
Loading…
x
Reference in New Issue
Block a user