mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-05 09:04:34 +02:00
feat(YouTube - Shorts components): add Custom actions
setting (YouTube 19.05.36+)
This commit is contained in:
parent
169dc9acba
commit
dad6b3d5e4
@ -0,0 +1,171 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
|
||||||
|
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
|
||||||
|
import app.revanced.extension.shared.patches.components.Filter;
|
||||||
|
import app.revanced.extension.shared.patches.components.StringFilterGroup;
|
||||||
|
import app.revanced.extension.shared.utils.Logger;
|
||||||
|
import app.revanced.extension.shared.utils.TrieSearch;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class ShortsCustomActionsFilter extends Filter {
|
||||||
|
private static final boolean IS_SPOOFING_TO_YOUTUBE_2023 =
|
||||||
|
isSpoofingToLessThan("19.00.00");
|
||||||
|
private static final boolean SHORTS_CUSTOM_ACTIONS_ENABLED =
|
||||||
|
!IS_SPOOFING_TO_YOUTUBE_2023 &&
|
||||||
|
Settings.ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
||||||
|
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
|
||||||
|
*/
|
||||||
|
@GuardedBy("itself")
|
||||||
|
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
|
||||||
|
/**
|
||||||
|
* Number of video id's to keep track of for searching thru the buffer.
|
||||||
|
* A minimum value of 3 should be sufficient, but check a few more just in case.
|
||||||
|
*/
|
||||||
|
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||||
|
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
|
private final StringFilterGroup playerFlyoutMenu;
|
||||||
|
|
||||||
|
private final StringFilterGroup likeDislikeButton;
|
||||||
|
|
||||||
|
public static volatile boolean isShortsFlyoutMenuVisible;
|
||||||
|
|
||||||
|
public ShortsCustomActionsFilter() {
|
||||||
|
likeDislikeButton = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"|shorts_like_button.eml",
|
||||||
|
"|shorts_dislike_button.eml"
|
||||||
|
);
|
||||||
|
playerFlyoutMenu = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"overflow_menu_item.eml|"
|
||||||
|
);
|
||||||
|
|
||||||
|
addIdentifierCallbacks(playerFlyoutMenu);
|
||||||
|
addPathCallbacks(likeDislikeButton);
|
||||||
|
|
||||||
|
// After the button identifiers is binary data and then the video id for that specific short.
|
||||||
|
videoIdFilterGroup.addAll(
|
||||||
|
new ByteArrayFilterGroup(null, "id.reel_like_button"),
|
||||||
|
new ByteArrayFilterGroup(null, "id.reel_dislike_button")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile static String shortsVideoId = "";
|
||||||
|
|
||||||
|
private static void setShortsVideoId(@NonNull String videoId, boolean isLive) {
|
||||||
|
if (shortsVideoId.equals(videoId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String prefix = isLive ? "New Short livestream video id: " : "New Short video id: ";
|
||||||
|
Logger.printDebug(() -> prefix + videoId);
|
||||||
|
shortsVideoId = videoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getShortsVideoId() {
|
||||||
|
return shortsVideoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void newShortsVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
|
||||||
|
@NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
|
||||||
|
final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newlyLoadedLiveStreamValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setShortsVideoId(newlyLoadedVideoId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||||
|
try {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isShortAndOpeningOrPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (lastVideoIds) {
|
||||||
|
lastVideoIds.putIfAbsent(videoId, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "newPlayerResponseVideoId failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This could use {@link TrieSearch}, but since the patterns are constantly changing
|
||||||
|
* the overhead of updating the Trie might negate the search performance gain.
|
||||||
|
*/
|
||||||
|
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
|
||||||
|
for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
|
||||||
|
boolean found = true;
|
||||||
|
for (int j = 0, textLength = text.length(); j < textLength; j++) {
|
||||||
|
if (array[i + j] != (byte) text.charAt(j)) {
|
||||||
|
found = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
|
||||||
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_ENABLED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (matchedGroup == playerFlyoutMenu) {
|
||||||
|
isShortsFlyoutMenuVisible = true;
|
||||||
|
findVideoId(protobufBufferArray);
|
||||||
|
} else if (matchedGroup == likeDislikeButton && videoIdFilterGroup.check(protobufBufferArray).isFiltered()) {
|
||||||
|
findVideoId(protobufBufferArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findVideoId(byte[] protobufBufferArray) {
|
||||||
|
synchronized (lastVideoIds) {
|
||||||
|
for (String videoId : lastVideoIds.keySet()) {
|
||||||
|
if (byteArrayContainsString(protobufBufferArray, videoId)) {
|
||||||
|
setShortsVideoId(videoId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,287 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.shorts;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.utils.ResourceUtils.getString;
|
||||||
|
import static app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter.isShortsFlyoutMenuVisible;
|
||||||
|
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.extension.shared.utils.Logger;
|
||||||
|
import app.revanced.extension.shared.utils.ResourceUtils;
|
||||||
|
import app.revanced.extension.shared.utils.Utils;
|
||||||
|
import app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
|
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class CustomActionsPatch {
|
||||||
|
private static final boolean IS_SPOOFING_TO_YOUTUBE_2023 =
|
||||||
|
isSpoofingToLessThan("19.00.00");
|
||||||
|
private static final boolean SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED =
|
||||||
|
!IS_SPOOFING_TO_YOUTUBE_2023 && Settings.ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU.get();
|
||||||
|
|
||||||
|
private static final int arrSize = CustomAction.values().length;
|
||||||
|
private static final Map<CustomAction, Object> flyoutMenuMap = new LinkedHashMap<>(arrSize);
|
||||||
|
private static WeakReference<Context> contextRef = new WeakReference<>(null);
|
||||||
|
private static WeakReference<RecyclerView> recyclerViewRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setFlyoutMenuObject(Object bottomSheetMenuObject) {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ShortsPlayerState.getCurrent().isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bottomSheetMenuObject == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (CustomAction customAction : CustomAction.values()) {
|
||||||
|
flyoutMenuMap.putIfAbsent(customAction, bottomSheetMenuObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void addFlyoutMenu(Object bottomSheetMenuClass, Object bottomSheetMenuList) {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ShortsPlayerState.getCurrent().isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (CustomAction customAction : CustomAction.values()) {
|
||||||
|
if (customAction.settings.get()) {
|
||||||
|
addFlyoutMenu(bottomSheetMenuClass, bottomSheetMenuList, customAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rest of the implementation added by patch.
|
||||||
|
*/
|
||||||
|
private static void addFlyoutMenu(Object bottomSheetMenuClass, Object bottomSheetMenuList, CustomAction customAction) {
|
||||||
|
Object bottomSheetMenuObject = flyoutMenuMap.get(customAction);
|
||||||
|
// These instructions are ignored by patch.
|
||||||
|
Logger.printInfo(() -> customAction.name() + bottomSheetMenuClass + bottomSheetMenuList + bottomSheetMenuObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void onFlyoutMenuCreate(final RecyclerView recyclerView) {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
|
||||||
|
try {
|
||||||
|
if (ShortsPlayerState.getCurrent().isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
contextRef = new WeakReference<>(recyclerView.getContext());
|
||||||
|
if (!isShortsFlyoutMenuVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int childCount = recyclerView.getChildCount();
|
||||||
|
if (childCount < arrSize + 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < arrSize; i++) {
|
||||||
|
if (recyclerView.getChildAt(childCount - i - 1) instanceof ViewGroup parentViewGroup) {
|
||||||
|
childCount = recyclerView.getChildCount();
|
||||||
|
if (childCount > 3 && parentViewGroup.getChildAt(1) instanceof TextView textView) {
|
||||||
|
for (CustomAction customAction : CustomAction.values()) {
|
||||||
|
if (customAction.getLabel().equals(textView.getText().toString())) {
|
||||||
|
View.OnClickListener onClick = customAction.getOnClickListener();
|
||||||
|
View.OnLongClickListener onLongClick = customAction.getOnLongClickListener();
|
||||||
|
recyclerViewRef = new WeakReference<>(recyclerView);
|
||||||
|
parentViewGroup.setOnClickListener(onClick);
|
||||||
|
if (onLongClick != null) {
|
||||||
|
parentViewGroup.setOnLongClickListener(onLongClick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isShortsFlyoutMenuVisible = false;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hideFlyoutMenu() {
|
||||||
|
if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RecyclerView recyclerView = recyclerViewRef.get();
|
||||||
|
if (recyclerView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
|
||||||
|
// This only shows in phone layout.
|
||||||
|
Utils.clickView(parentView4th.getChildAt(0));
|
||||||
|
|
||||||
|
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
|
||||||
|
parentView3rd.setVisibility(View.GONE);
|
||||||
|
parentView4th.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CustomAction {
|
||||||
|
COPY_URL(
|
||||||
|
Settings.SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL,
|
||||||
|
"yt_outline_link_black_24",
|
||||||
|
() -> VideoUtils.copyUrl(
|
||||||
|
VideoUtils.getVideoUrl(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId(),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
() -> VideoUtils.copyUrl(
|
||||||
|
VideoUtils.getVideoUrl(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
),
|
||||||
|
COPY_URL_WITH_TIMESTAMP(
|
||||||
|
Settings.SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP,
|
||||||
|
"yt_outline_arrow_time_black_24",
|
||||||
|
() -> VideoUtils.copyUrl(
|
||||||
|
VideoUtils.getVideoUrl(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
() -> VideoUtils.copyUrl(
|
||||||
|
VideoUtils.getVideoUrl(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId(),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
EXTERNAL_DOWNLOADER(
|
||||||
|
Settings.SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER,
|
||||||
|
"yt_outline_download_black_24",
|
||||||
|
() -> VideoUtils.launchVideoExternalDownloader(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
OPEN_VIDEO(
|
||||||
|
Settings.SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO,
|
||||||
|
"yt_outline_youtube_logo_icon_black_24",
|
||||||
|
() -> VideoUtils.openVideo(
|
||||||
|
ShortsCustomActionsFilter.getShortsVideoId(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
),
|
||||||
|
REPEAT_STATE(
|
||||||
|
Settings.SHORTS_CUSTOM_ACTIONS_REPEAT_STATE,
|
||||||
|
"yt_outline_arrow_repeat_1_black_24",
|
||||||
|
() -> VideoUtils.showShortsRepeatDialog(contextRef.get())
|
||||||
|
);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final BooleanSetting settings;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Drawable drawable;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Runnable onClickAction;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Runnable onLongClickAction;
|
||||||
|
|
||||||
|
CustomAction(@NonNull BooleanSetting settings,
|
||||||
|
@NonNull String icon,
|
||||||
|
@NonNull Runnable onClickAction
|
||||||
|
) {
|
||||||
|
this(settings, icon, onClickAction, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomAction(@NonNull BooleanSetting settings,
|
||||||
|
@NonNull String icon,
|
||||||
|
@NonNull Runnable onClickAction,
|
||||||
|
@Nullable Runnable onLongClickAction
|
||||||
|
) {
|
||||||
|
this.drawable = Objects.requireNonNull(ResourceUtils.getDrawable(icon));
|
||||||
|
this.label = getString(settings.key + "_label");
|
||||||
|
this.settings = settings;
|
||||||
|
this.onClickAction = onClickAction;
|
||||||
|
this.onLongClickAction = onLongClickAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Drawable getDrawable() {
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Runnable getOnClickAction() {
|
||||||
|
return onClickAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public View.OnClickListener getOnClickListener() {
|
||||||
|
return v -> {
|
||||||
|
hideFlyoutMenu();
|
||||||
|
onClickAction.run();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public View.OnLongClickListener getOnLongClickListener() {
|
||||||
|
if (onLongClickAction == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return v -> {
|
||||||
|
hideFlyoutMenu();
|
||||||
|
onLongClickAction.run();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -464,10 +464,23 @@ public class Settings extends BaseSettings {
|
|||||||
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_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", TRUE);
|
||||||
|
|
||||||
|
// PreferenceScreen: Shorts - Shorts player components - Animation / Feedback
|
||||||
public static final BooleanSetting DISABLE_SHORTS_LIKE_BUTTON_FOUNTAIN_ANIMATION = new BooleanSetting("revanced_disable_shorts_like_button_fountain_animation", FALSE);
|
public static final BooleanSetting DISABLE_SHORTS_LIKE_BUTTON_FOUNTAIN_ANIMATION = new BooleanSetting("revanced_disable_shorts_like_button_fountain_animation", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_PLAY_PAUSE_BUTTON_BACKGROUND = new BooleanSetting("revanced_hide_shorts_play_pause_button_background", FALSE, true);
|
public static final BooleanSetting HIDE_SHORTS_PLAY_PAUSE_BUTTON_BACKGROUND = new BooleanSetting("revanced_hide_shorts_play_pause_button_background", FALSE, true);
|
||||||
public static final EnumSetting<AnimationType> ANIMATION_TYPE = new EnumSetting<>("revanced_shorts_double_tap_to_like_animation", AnimationType.ORIGINAL, true);
|
public static final EnumSetting<AnimationType> ANIMATION_TYPE = new EnumSetting<>("revanced_shorts_double_tap_to_like_animation", AnimationType.ORIGINAL, true);
|
||||||
|
|
||||||
|
// PreferenceScreen: Shorts - Shorts player components - Custom actions
|
||||||
|
public static final BooleanSetting ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU = new BooleanSetting("revanced_enable_shorts_custom_actions_flyout_menu", FALSE, true);
|
||||||
|
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL = new BooleanSetting("revanced_shorts_custom_actions_copy_video_url", FALSE, true,
|
||||||
|
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU));
|
||||||
|
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_shorts_custom_actions_copy_video_url_timestamp", FALSE, true,
|
||||||
|
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU));
|
||||||
|
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_shorts_custom_actions_external_downloader", FALSE, true,
|
||||||
|
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU));
|
||||||
|
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO = new BooleanSetting("revanced_shorts_custom_actions_open_video", FALSE, true,
|
||||||
|
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU));
|
||||||
|
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_REPEAT_STATE = new BooleanSetting("revanced_shorts_custom_actions_repeat_state", FALSE, true,
|
||||||
|
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU));
|
||||||
|
|
||||||
// Experimental Flags
|
// Experimental Flags
|
||||||
public static final BooleanSetting ENABLE_TIME_STAMP = new BooleanSetting("revanced_enable_shorts_time_stamp", FALSE, true);
|
public static final BooleanSetting ENABLE_TIME_STAMP = new BooleanSetting("revanced_enable_shorts_time_stamp", FALSE, true);
|
||||||
|
@ -31,7 +31,8 @@ import app.revanced.extension.youtube.shared.VideoInformation;
|
|||||||
public class VideoUtils extends IntentUtils {
|
public class VideoUtils extends IntentUtils {
|
||||||
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
||||||
private static final String VIDEO_URL = "https://youtu.be/";
|
private static final String VIDEO_URL = "https://youtu.be/";
|
||||||
private static final String VIDEO_SCHEME_FORMAT = "vnd.youtube://%s?start=%d";
|
private static final String VIDEO_SCHEME_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
|
||||||
|
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%d";
|
||||||
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
||||||
|
|
||||||
private static String getPlaylistUrl(String playlistId) {
|
private static String getPlaylistUrl(String playlistId) {
|
||||||
@ -46,7 +47,7 @@ public class VideoUtils extends IntentUtils {
|
|||||||
return getVideoUrl(VideoInformation.getVideoId(), withTimestamp);
|
return getVideoUrl(VideoInformation.getVideoId(), withTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getVideoUrl(String videoId, boolean withTimestamp) {
|
public static String getVideoUrl(String videoId, boolean withTimestamp) {
|
||||||
StringBuilder builder = new StringBuilder(VIDEO_URL);
|
StringBuilder builder = new StringBuilder(VIDEO_URL);
|
||||||
builder.append(videoId);
|
builder.append(videoId);
|
||||||
final long currentVideoTimeInSeconds = VideoInformation.getVideoTimeInSeconds();
|
final long currentVideoTimeInSeconds = VideoInformation.getVideoTimeInSeconds();
|
||||||
@ -58,15 +59,24 @@ public class VideoUtils extends IntentUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String getVideoScheme() {
|
private static String getVideoScheme() {
|
||||||
return getVideoScheme(VideoInformation.getVideoId());
|
return getVideoScheme(VideoInformation.getVideoId(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getVideoScheme(String videoId) {
|
private static String getVideoScheme(String videoId, boolean isShorts) {
|
||||||
return String.format(Locale.ENGLISH, VIDEO_SCHEME_FORMAT, videoId, VideoInformation.getVideoTimeInSeconds());
|
return String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
isShorts ? VIDEO_SCHEME_INTENT_FORMAT : VIDEO_SCHEME_LINK_FORMAT,
|
||||||
|
videoId,
|
||||||
|
VideoInformation.getVideoTimeInSeconds()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyUrl(boolean withTimestamp) {
|
public static void copyUrl(boolean withTimestamp) {
|
||||||
setClipboard(getVideoUrl(withTimestamp), withTimestamp
|
copyUrl(getVideoUrl(withTimestamp), withTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyUrl(String videoUrl, boolean withTimestamp) {
|
||||||
|
setClipboard(videoUrl, withTimestamp
|
||||||
? str("revanced_share_copy_url_timestamp_success")
|
? str("revanced_share_copy_url_timestamp_success")
|
||||||
: str("revanced_share_copy_url_success")
|
: str("revanced_share_copy_url_success")
|
||||||
);
|
);
|
||||||
@ -118,7 +128,11 @@ public class VideoUtils extends IntentUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void openVideo(@NonNull String videoId) {
|
public static void openVideo(@NonNull String videoId) {
|
||||||
openVideo(getVideoScheme(videoId), "");
|
openVideo(getVideoScheme(videoId, false), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openVideo(@NonNull String videoId, boolean isShorts) {
|
||||||
|
openVideo(getVideoScheme(videoId, isShorts), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openVideo(@NonNull PlaylistIdPrefix prefixId) {
|
public static void openVideo(@NonNull PlaylistIdPrefix prefixId) {
|
||||||
|
@ -5,16 +5,17 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.patch.PatchException
|
import app.revanced.patcher.patch.PatchException
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.youtube.utils.bottomSheetMenuItemBuilderFingerprint
|
||||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||||
import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR
|
import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR
|
||||||
|
import app.revanced.patches.youtube.utils.indexOfSpannedCharSequenceInstruction
|
||||||
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_FEED_FLYOUT_MENU
|
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_FEED_FLYOUT_MENU
|
||||||
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||||
import app.revanced.util.fingerprint.matchOrNull
|
|
||||||
import app.revanced.util.fingerprint.matchOrThrow
|
import app.revanced.util.fingerprint.matchOrThrow
|
||||||
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
@ -33,27 +34,16 @@ val feedFlyoutMenuPatch = bytecodePatch(
|
|||||||
|
|
||||||
// region patch for phone
|
// region patch for phone
|
||||||
|
|
||||||
val bottomSheetMenuItemBuilderMatch =
|
bottomSheetMenuItemBuilderFingerprint.methodOrThrow().apply {
|
||||||
bottomSheetMenuItemBuilderLegacyFingerprint.matchOrNull()
|
val insertIndex = indexOfSpannedCharSequenceInstruction(this) + 2
|
||||||
?: bottomSheetMenuItemBuilderFingerprint.matchOrThrow()
|
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
|
||||||
|
|
||||||
bottomSheetMenuItemBuilderMatch.let {
|
addInstructions(
|
||||||
it.method.apply {
|
insertIndex, """
|
||||||
val targetIndex = it.patternMatch!!.endIndex
|
invoke-static {v$insertRegister}, $FEED_CLASS_DESCRIPTOR->hideFlyoutMenu(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
||||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
move-result-object v$insertRegister
|
||||||
|
"""
|
||||||
val targetParameter =
|
)
|
||||||
getInstruction<ReferenceInstruction>(targetIndex - 1).reference
|
|
||||||
if (!targetParameter.toString().endsWith("Ljava/lang/CharSequence;"))
|
|
||||||
throw PatchException("Method signature parameter did not match: $targetParameter")
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
targetIndex + 1, """
|
|
||||||
invoke-static {v$targetRegister}, $FEED_CLASS_DESCRIPTOR->hideFlyoutMenu(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
|
||||||
move-result-object v$targetRegister
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -6,39 +6,6 @@ import app.revanced.util.or
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
/**
|
|
||||||
* Compatible with YouTube v19.11.43~
|
|
||||||
*/
|
|
||||||
internal val bottomSheetMenuItemBuilderFingerprint = legacyFingerprint(
|
|
||||||
name = "bottomSheetMenuItemBuilderFingerprint",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
returnType = "L",
|
|
||||||
parameters = listOf("L"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT
|
|
||||||
),
|
|
||||||
strings = listOf("Text missing for BottomSheetMenuItem with iconType: ")
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compatible with ~YouTube v19.10.39
|
|
||||||
*/
|
|
||||||
internal val bottomSheetMenuItemBuilderLegacyFingerprint = legacyFingerprint(
|
|
||||||
name = "bottomSheetMenuItemBuilderLegacyFingerprint",
|
|
||||||
returnType = "L",
|
|
||||||
parameters = listOf("L"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT
|
|
||||||
),
|
|
||||||
strings = listOf("ElementTransformer, ElementPresenter and InteractionLogger cannot be null")
|
|
||||||
)
|
|
||||||
|
|
||||||
internal val contextualMenuItemBuilderFingerprint = legacyFingerprint(
|
internal val contextualMenuItemBuilderFingerprint = legacyFingerprint(
|
||||||
name = "contextualMenuItemBuilderFingerprint",
|
name = "contextualMenuItemBuilderFingerprint",
|
||||||
returnType = "V",
|
returnType = "V",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app.revanced.patches.youtube.shorts.components
|
package app.revanced.patches.youtube.shorts.components
|
||||||
|
|
||||||
import app.revanced.patches.youtube.utils.resourceid.badgeLabel
|
import app.revanced.patches.youtube.utils.resourceid.badgeLabel
|
||||||
import app.revanced.patches.youtube.utils.resourceid.bottomBarContainer
|
|
||||||
import app.revanced.patches.youtube.utils.resourceid.metaPanel
|
import app.revanced.patches.youtube.utils.resourceid.metaPanel
|
||||||
import app.revanced.patches.youtube.utils.resourceid.reelDynRemix
|
import app.revanced.patches.youtube.utils.resourceid.reelDynRemix
|
||||||
import app.revanced.patches.youtube.utils.resourceid.reelDynShare
|
import app.revanced.patches.youtube.utils.resourceid.reelDynShare
|
||||||
@ -19,14 +18,18 @@ import app.revanced.util.or
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
// multiFingerprint
|
internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint(
|
||||||
internal val bottomBarContainerHeightFingerprint = legacyFingerprint(
|
name = "bottomSheetMenuListBuilderFingerprint",
|
||||||
name = "bottomBarContainerHeightFingerprint",
|
returnType = "L",
|
||||||
returnType = "V",
|
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
parameters = emptyList(),
|
||||||
parameters = listOf("Landroid/view/View;", "Landroid/os/Bundle;"),
|
opcodes = listOf(
|
||||||
strings = listOf("r_pfvc"),
|
Opcode.IGET_OBJECT,
|
||||||
literals = listOf(bottomBarContainer),
|
Opcode.INVOKE_VIRTUAL,
|
||||||
|
Opcode.MOVE_RESULT,
|
||||||
|
Opcode.IF_EQZ,
|
||||||
|
),
|
||||||
|
strings = listOf("Bottom Sheet Menu is empty. No menu items were supported."),
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val reelEnumConstructorFingerprint = legacyFingerprint(
|
internal val reelEnumConstructorFingerprint = legacyFingerprint(
|
||||||
|
@ -6,6 +6,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||||
import app.revanced.patcher.patch.PatchException
|
import app.revanced.patcher.patch.PatchException
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
@ -14,11 +15,13 @@ import app.revanced.patches.shared.litho.addLithoFilter
|
|||||||
import app.revanced.patches.shared.litho.lithoFilterPatch
|
import app.revanced.patches.shared.litho.lithoFilterPatch
|
||||||
import app.revanced.patches.shared.textcomponent.hookSpannableString
|
import app.revanced.patches.shared.textcomponent.hookSpannableString
|
||||||
import app.revanced.patches.shared.textcomponent.textComponentPatch
|
import app.revanced.patches.shared.textcomponent.textComponentPatch
|
||||||
|
import app.revanced.patches.youtube.utils.bottomSheetMenuItemBuilderFingerprint
|
||||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||||
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
||||||
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_CLASS_DESCRIPTOR
|
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_CLASS_DESCRIPTOR
|
||||||
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_PATH
|
import app.revanced.patches.youtube.utils.extension.Constants.SHORTS_PATH
|
||||||
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
|
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
|
||||||
|
import app.revanced.patches.youtube.utils.indexOfSpannedCharSequenceInstruction
|
||||||
import app.revanced.patches.youtube.utils.lottie.LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR
|
import app.revanced.patches.youtube.utils.lottie.LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR
|
||||||
import app.revanced.patches.youtube.utils.lottie.lottieAnimationViewHookPatch
|
import app.revanced.patches.youtube.utils.lottie.lottieAnimationViewHookPatch
|
||||||
import app.revanced.patches.youtube.utils.navigation.addBottomBarContainerHook
|
import app.revanced.patches.youtube.utils.navigation.addBottomBarContainerHook
|
||||||
@ -26,9 +29,12 @@ import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch
|
|||||||
import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS
|
import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS
|
||||||
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
|
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
|
||||||
import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater
|
import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater
|
||||||
|
import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater
|
||||||
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
|
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
|
||||||
import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater
|
import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater
|
||||||
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
|
||||||
|
import app.revanced.patches.youtube.utils.recyclerview.bottomSheetRecyclerViewHook
|
||||||
|
import app.revanced.patches.youtube.utils.recyclerview.bottomSheetRecyclerViewPatch
|
||||||
import app.revanced.patches.youtube.utils.resourceid.bottomBarContainer
|
import app.revanced.patches.youtube.utils.resourceid.bottomBarContainer
|
||||||
import app.revanced.patches.youtube.utils.resourceid.metaPanel
|
import app.revanced.patches.youtube.utils.resourceid.metaPanel
|
||||||
import app.revanced.patches.youtube.utils.resourceid.reelDynRemix
|
import app.revanced.patches.youtube.utils.resourceid.reelDynRemix
|
||||||
@ -49,9 +55,13 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext
|
|||||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||||
import app.revanced.patches.youtube.video.information.hookShortsVideoInformation
|
import app.revanced.patches.youtube.video.information.hookShortsVideoInformation
|
||||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||||
|
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
||||||
|
import app.revanced.patches.youtube.video.videoid.videoIdPatch
|
||||||
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
|
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
|
||||||
import app.revanced.util.ResourceGroup
|
import app.revanced.util.ResourceGroup
|
||||||
|
import app.revanced.util.cloneMutable
|
||||||
import app.revanced.util.copyResources
|
import app.revanced.util.copyResources
|
||||||
|
import app.revanced.util.findMethodOrThrow
|
||||||
import app.revanced.util.findMutableMethodOf
|
import app.revanced.util.findMutableMethodOf
|
||||||
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||||
import app.revanced.util.fingerprint.matchOrThrow
|
import app.revanced.util.fingerprint.matchOrThrow
|
||||||
@ -73,6 +83,7 @@ import com.android.tools.smali.dexlib2.AccessFlags
|
|||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
@ -130,6 +141,153 @@ private val shortsAnimationPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SHORTS_PLAYER_FLYOUT_MENU_FILTER_CLASS_DESCRIPTOR =
|
||||||
|
"$COMPONENTS_PATH/ShortsCustomActionsFilter;"
|
||||||
|
private const val EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR =
|
||||||
|
"$SHORTS_PATH/CustomActionsPatch;"
|
||||||
|
|
||||||
|
private val shortsCustomActionsPatch = bytecodePatch(
|
||||||
|
description = "shortsCustomActionsPatch"
|
||||||
|
) {
|
||||||
|
dependsOn(
|
||||||
|
bottomSheetRecyclerViewPatch,
|
||||||
|
lithoFilterPatch,
|
||||||
|
playerTypeHookPatch,
|
||||||
|
videoIdPatch,
|
||||||
|
videoInformationPatch,
|
||||||
|
versionCheckPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
if (!is_18_49_or_greater) {
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetMenuListBuilderFingerprint.matchOrThrow().let {
|
||||||
|
it.method.apply {
|
||||||
|
val addListIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
getReference<MethodReference>()?.name == "add"
|
||||||
|
}
|
||||||
|
val addListReference = getInstruction<ReferenceInstruction>(addListIndex).reference
|
||||||
|
|
||||||
|
val getObjectIndex = indexOfFirstInstructionReversedOrThrow(addListIndex) {
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
getReference<MethodReference>()?.returnType == "Ljava/lang/Object;"
|
||||||
|
}
|
||||||
|
val getObjectReference = getInstruction<ReferenceInstruction>(getObjectIndex).reference as MethodReference
|
||||||
|
|
||||||
|
val bottomSheetMenuInitializeIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
opcode == Opcode.INVOKE_STATIC_RANGE &&
|
||||||
|
reference?.returnType == "V" &&
|
||||||
|
reference.parameterTypes[1] == "Ljava/lang/Object;"
|
||||||
|
}
|
||||||
|
val bottomSheetMenuObjectRegister = getInstruction<RegisterRangeInstruction>(bottomSheetMenuInitializeIndex).startRegister
|
||||||
|
val bottomSheetMenuObject = (getInstruction<ReferenceInstruction>(bottomSheetMenuInitializeIndex).reference as MethodReference).parameterTypes[0]!!
|
||||||
|
|
||||||
|
val bottomSheetMenuListIndex = it.patternMatch!!.startIndex
|
||||||
|
val bottomSheetMenuListField = (getInstruction<ReferenceInstruction>(bottomSheetMenuListIndex).reference as FieldReference)
|
||||||
|
|
||||||
|
val bottomSheetMenuClass = bottomSheetMenuListField.definingClass
|
||||||
|
val bottomSheetMenuList = bottomSheetMenuListField.type
|
||||||
|
|
||||||
|
val bottomSheetMenuClassRegister = getInstruction<TwoRegisterInstruction>(bottomSheetMenuListIndex).registerB
|
||||||
|
val bottomSheetMenuListRegister = getInstruction<TwoRegisterInstruction>(bottomSheetMenuListIndex).registerA
|
||||||
|
|
||||||
|
addInstruction(
|
||||||
|
bottomSheetMenuListIndex + 1,
|
||||||
|
"invoke-static {v$bottomSheetMenuClassRegister, v$bottomSheetMenuListRegister}, " +
|
||||||
|
"$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->addFlyoutMenu(Ljava/lang/Object;Ljava/lang/Object;)V"
|
||||||
|
)
|
||||||
|
|
||||||
|
addInstruction(
|
||||||
|
bottomSheetMenuInitializeIndex + 1,
|
||||||
|
"invoke-static {v$bottomSheetMenuObjectRegister}, " +
|
||||||
|
"$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->setFlyoutMenuObject(Ljava/lang/Object;)V"
|
||||||
|
)
|
||||||
|
|
||||||
|
val addFlyoutMenuMethod = findMethodOrThrow(EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR) {
|
||||||
|
name == "addFlyoutMenu" &&
|
||||||
|
accessFlags == AccessFlags.PRIVATE or AccessFlags.STATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
val customActionClass = with(addFlyoutMenuMethod) {
|
||||||
|
val thirdParameter = parameters[2]
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
3, """
|
||||||
|
check-cast p0, $bottomSheetMenuClass
|
||||||
|
check-cast v0, $bottomSheetMenuObject
|
||||||
|
invoke-virtual {p0, v0, p2}, $bottomSheetMenuClass->buildFlyoutMenu(${bottomSheetMenuObject}${thirdParameter})${getObjectReference.definingClass}
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {v0}, $getObjectReference
|
||||||
|
move-result-object v0
|
||||||
|
check-cast p1, $bottomSheetMenuList
|
||||||
|
invoke-virtual {p1, v0}, $addListReference
|
||||||
|
return-void
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
thirdParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
val bottomSheetMenuItemBuilderMethod = bottomSheetMenuItemBuilderFingerprint
|
||||||
|
.methodOrThrow()
|
||||||
|
|
||||||
|
val newParameter = bottomSheetMenuItemBuilderMethod.parameters + listOf(customActionClass)
|
||||||
|
|
||||||
|
it.classDef.methods.add(
|
||||||
|
bottomSheetMenuItemBuilderMethod
|
||||||
|
.cloneMutable(
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
name = "buildFlyoutMenu",
|
||||||
|
registerCount = bottomSheetMenuItemBuilderMethod.implementation!!.registerCount + 1,
|
||||||
|
parameters = newParameter,
|
||||||
|
).apply {
|
||||||
|
val drawableIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.INVOKE_DIRECT &&
|
||||||
|
getReference<MethodReference>()?.returnType == "Landroid/graphics/drawable/Drawable;"
|
||||||
|
}
|
||||||
|
val drawableRegister = getInstruction<OneRegisterInstruction>(drawableIndex + 1).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
drawableIndex + 2, """
|
||||||
|
invoke-virtual {p2}, $customActionClass->getDrawable()Landroid/graphics/drawable/Drawable;
|
||||||
|
move-result-object v$drawableRegister
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
val charSequenceIndex = indexOfSpannedCharSequenceInstruction(this)
|
||||||
|
val charSequenceRegister = getInstruction<OneRegisterInstruction>(charSequenceIndex + 1).registerA
|
||||||
|
|
||||||
|
val insertIndex = charSequenceIndex + 2
|
||||||
|
|
||||||
|
if (getInstruction<ReferenceInstruction>(insertIndex).reference.toString().startsWith("Lapp/revanced")) {
|
||||||
|
removeInstructions(insertIndex, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
insertIndex, """
|
||||||
|
invoke-virtual {p2}, $customActionClass->getLabel()Ljava/lang/String;
|
||||||
|
move-result-object v$charSequenceRegister
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetRecyclerViewHook("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V")
|
||||||
|
|
||||||
|
hookPlayerResponseVideoId("$SHORTS_PLAYER_FLYOUT_MENU_FILTER_CLASS_DESCRIPTOR->newPlayerResponseVideoId(Ljava/lang/String;Z)V")
|
||||||
|
hookShortsVideoInformation("$SHORTS_PLAYER_FLYOUT_MENU_FILTER_CLASS_DESCRIPTOR->newShortsVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V")
|
||||||
|
|
||||||
|
addLithoFilter(SHORTS_PLAYER_FLYOUT_MENU_FILTER_CLASS_DESCRIPTOR)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val shortsNavigationBarPatch = bytecodePatch(
|
private val shortsNavigationBarPatch = bytecodePatch(
|
||||||
description = "shortsNavigationBarPatch"
|
description = "shortsNavigationBarPatch"
|
||||||
) {
|
) {
|
||||||
@ -344,6 +502,7 @@ val shortsComponentPatch = bytecodePatch(
|
|||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
shortsAnimationPatch,
|
shortsAnimationPatch,
|
||||||
|
shortsCustomActionsPatch,
|
||||||
shortsNavigationBarPatch,
|
shortsNavigationBarPatch,
|
||||||
shortsRepeatPatch,
|
shortsRepeatPatch,
|
||||||
shortsTimeStampPatch,
|
shortsTimeStampPatch,
|
||||||
@ -412,6 +571,10 @@ val shortsComponentPatch = bytecodePatch(
|
|||||||
settingArray += "SETTINGS: SHORTS_TIME_STAMP"
|
settingArray += "SETTINGS: SHORTS_TIME_STAMP"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_18_49_or_greater) {
|
||||||
|
settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS"
|
||||||
|
}
|
||||||
|
|
||||||
// region patch for hide comments button (non-litho)
|
// region patch for hide comments button (non-litho)
|
||||||
|
|
||||||
shortsButtonFingerprint.hideButton(rightComment, "hideShortsCommentsButton", false)
|
shortsButtonFingerprint.hideButton(rightComment, "hideShortsCommentsButton", false)
|
||||||
|
@ -21,6 +21,31 @@ import com.android.tools.smali.dexlib2.Opcode
|
|||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
internal val bottomSheetMenuItemBuilderFingerprint = legacyFingerprint(
|
||||||
|
name = "bottomSheetMenuItemBuilderFingerprint",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
returnType = "L",
|
||||||
|
parameters = listOf("L"),
|
||||||
|
opcodes = listOf(
|
||||||
|
Opcode.INVOKE_STATIC,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
|
Opcode.INVOKE_STATIC,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT
|
||||||
|
),
|
||||||
|
strings = listOf("Text missing for BottomSheetMenuItem."),
|
||||||
|
customFingerprint = { method, _ ->
|
||||||
|
indexOfSpannedCharSequenceInstruction(method) >= 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fun indexOfSpannedCharSequenceInstruction(method: Method) =
|
||||||
|
method.indexOfFirstInstruction {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
opcode == Opcode.INVOKE_STATIC &&
|
||||||
|
reference?.parameterTypes?.size == 1 &&
|
||||||
|
reference.returnType == "Ljava/lang/CharSequence;"
|
||||||
|
}
|
||||||
|
|
||||||
internal val engagementPanelBuilderFingerprint = legacyFingerprint(
|
internal val engagementPanelBuilderFingerprint = legacyFingerprint(
|
||||||
name = "engagementPanelBuilderFingerprint",
|
name = "engagementPanelBuilderFingerprint",
|
||||||
returnType = "L",
|
returnType = "L",
|
||||||
|
@ -1041,7 +1041,7 @@ Tap and hold to copy video URL with timestamp."</string>
|
|||||||
Tap and hold to copy video timestamp."</string>
|
Tap and hold to copy video timestamp."</string>
|
||||||
<string name="revanced_overlay_button_mute_volume_title">Show mute volume button</string>
|
<string name="revanced_overlay_button_mute_volume_title">Show mute volume button</string>
|
||||||
<string name="revanced_overlay_button_mute_volume_summary">Tap to mute volume of the current video. Tap again to unmute.</string>
|
<string name="revanced_overlay_button_mute_volume_summary">Tap to mute volume of the current video. Tap again to unmute.</string>
|
||||||
<string name="revanced_overlay_button_external_downloader_title">Show external download button</string>
|
<string name="revanced_overlay_button_external_downloader_title">Show external downloader button</string>
|
||||||
<string name="revanced_overlay_button_external_downloader_summary">Tap to launch external downloader.</string>
|
<string name="revanced_overlay_button_external_downloader_summary">Tap to launch external downloader.</string>
|
||||||
<string name="revanced_overlay_button_speed_dialog_title">Show speed dialog button</string>
|
<string name="revanced_overlay_button_speed_dialog_title">Show speed dialog button</string>
|
||||||
<string name="revanced_overlay_button_speed_dialog_summary">"Tap to open speed dialog.
|
<string name="revanced_overlay_button_speed_dialog_summary">"Tap to open speed dialog.
|
||||||
@ -1354,6 +1354,37 @@ Info:
|
|||||||
<string name="revanced_shorts_double_tap_to_like_animation_entry_5">Heart (Tint)</string>
|
<string name="revanced_shorts_double_tap_to_like_animation_entry_5">Heart (Tint)</string>
|
||||||
<string name="revanced_shorts_double_tap_to_like_animation_entry_6">Hidden</string>
|
<string name="revanced_shorts_double_tap_to_like_animation_entry_6">Hidden</string>
|
||||||
|
|
||||||
|
<!-- PreferenceScreen: Shorts, PreferenceCategory: Shorts, PreferenceScreen: Shorts player, PreferenceCategory: Custom actions -->
|
||||||
|
<string name="revanced_preference_category_custom_actions">Custom actions</string>
|
||||||
|
<string name="revanced_enable_shorts_custom_actions_flyout_menu_title">Enable custom actions in flyout menu</string>
|
||||||
|
<string name="revanced_enable_shorts_custom_actions_flyout_menu_summary_on">"Custom actions are enabled in flyout menu.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
• Does not work if app version is spoofed to 18.49.37 or earlier.
|
||||||
|
• Does not work with livestream."</string>
|
||||||
|
<string name="revanced_enable_shorts_custom_actions_flyout_menu_summary_off">Custom actions are disabled in flyout menu.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_toolbar_dialog_title">Custom actions</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_label">Copy video URL</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_title">Show copy video URL menu</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_summary_on">Copy video URL menu is shown.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_summary_off">Copy video URL menu is hidden.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_timestamp_label">Copy timestamp URL</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_timestamp_title">Show copy timestamp URL menu</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_timestamp_summary_on">Copy timestamp URL menu is shown.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_copy_video_url_timestamp_summary_off">Copy timestamp URL menu is hidden.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_external_downloader_label">External downloader</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_external_downloader_title">Show external downloader menu</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_external_downloader_summary_on">External downloader menu is shown.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_external_downloader_summary_off">External downloader menu is hidden.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_open_video_label">Open video</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_open_video_title">Show open video menu</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_open_video_summary_on">Open video menu is shown.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_open_video_summary_off">Open video menu is hidden.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_repeat_state_label">Repeat state</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_repeat_state_title">Show repeat state menu</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_repeat_state_summary_on">Repeat state menu is shown.</string>
|
||||||
|
<string name="revanced_shorts_custom_actions_repeat_state_summary_off">Repeat state menu is hidden.</string>
|
||||||
|
|
||||||
<!-- PreferenceScreen: Shorts, PreferenceCategory: Shorts, PreferenceScreen: Shorts player, PreferenceCategory: Experimental Flags -->
|
<!-- PreferenceScreen: Shorts, PreferenceCategory: Shorts, PreferenceScreen: Shorts player, PreferenceCategory: Experimental Flags -->
|
||||||
<string name="revanced_enable_shorts_time_stamp_title">Enable timestamps</string>
|
<string name="revanced_enable_shorts_time_stamp_title">Enable timestamps</string>
|
||||||
<string name="revanced_enable_shorts_time_stamp_summary_on">"Timestamp is enabled.
|
<string name="revanced_enable_shorts_time_stamp_summary_on">"Timestamp is enabled.
|
||||||
|
@ -580,9 +580,18 @@
|
|||||||
<PreferenceCategory android:title="@string/revanced_preference_category_animation_feedback" android:layout="@layout/revanced_settings_preferences_category"/>
|
<PreferenceCategory android:title="@string/revanced_preference_category_animation_feedback" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||||
<SwitchPreference android:title="@string/revanced_disable_shorts_like_button_fountain_animation_title" android:key="revanced_disable_shorts_like_button_fountain_animation" android:summaryOn="@string/revanced_disable_shorts_like_button_fountain_animation_summary_on" android:summaryOff="@string/revanced_disable_shorts_like_button_fountain_animation_summary_off" />
|
<SwitchPreference android:title="@string/revanced_disable_shorts_like_button_fountain_animation_title" android:key="revanced_disable_shorts_like_button_fountain_animation" android:summaryOn="@string/revanced_disable_shorts_like_button_fountain_animation_summary_on" android:summaryOff="@string/revanced_disable_shorts_like_button_fountain_animation_summary_off" />
|
||||||
<SwitchPreference android:title="@string/revanced_hide_shorts_play_pause_button_background_title" android:key="revanced_hide_shorts_play_pause_button_background" android:summaryOn="@string/revanced_hide_shorts_play_pause_button_background_summary_on" android:summaryOff="@string/revanced_hide_shorts_play_pause_button_background_summary_off" />
|
<SwitchPreference android:title="@string/revanced_hide_shorts_play_pause_button_background_title" android:key="revanced_hide_shorts_play_pause_button_background" android:summaryOn="@string/revanced_hide_shorts_play_pause_button_background_summary_on" android:summaryOff="@string/revanced_hide_shorts_play_pause_button_background_summary_off" />
|
||||||
<ListPreference android:entries="@array/revanced_shorts_double_tap_to_like_animation_entries" android:title="@string/revanced_shorts_double_tap_to_like_animation_title" android:key="revanced_shorts_double_tap_to_like_animation" android:entryValues="@array/revanced_shorts_double_tap_to_like_animation_entry_values" />
|
<ListPreference android:entries="@array/revanced_shorts_double_tap_to_like_animation_entries" android:title="@string/revanced_shorts_double_tap_to_like_animation_title" android:key="revanced_shorts_double_tap_to_like_animation" android:entryValues="@array/revanced_shorts_double_tap_to_like_animation_entry_values" />SETTINGS: SHORTS_COMPONENTS -->
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>SETTINGS: SHORTS_COMPONENTS -->
|
<!-- SETTINGS: SHORTS_CUSTOM_ACTIONS
|
||||||
|
<PreferenceCategory android:title="@string/revanced_preference_category_custom_actions" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||||
|
<SwitchPreference android:title="@string/revanced_enable_shorts_custom_actions_flyout_menu_title" android:key="revanced_enable_shorts_custom_actions_flyout_menu" android:summaryOn="@string/revanced_enable_shorts_custom_actions_flyout_menu_summary_on" android:summaryOff="@string/revanced_enable_shorts_custom_actions_flyout_menu_summary_off" />
|
||||||
|
<SwitchPreference android:title="@string/revanced_shorts_custom_actions_copy_video_url_title" android:key="revanced_shorts_custom_actions_copy_video_url" android:summaryOn="@string/revanced_shorts_custom_actions_copy_video_url_summary_on" android:summaryOff="@string/revanced_shorts_custom_actions_copy_video_url_summary_off" />
|
||||||
|
<SwitchPreference android:title="@string/revanced_shorts_custom_actions_copy_video_url_timestamp_title" android:key="revanced_shorts_custom_actions_copy_video_url_timestamp" android:summaryOn="@string/revanced_shorts_custom_actions_copy_video_url_timestamp_summary_on" android:summaryOff="@string/revanced_shorts_custom_actions_copy_video_url_timestamp_summary_off" />
|
||||||
|
<SwitchPreference android:title="@string/revanced_shorts_custom_actions_external_downloader_title" android:key="revanced_shorts_custom_actions_external_downloader" android:summaryOn="@string/revanced_shorts_custom_actions_external_downloader_summary_on" android:summaryOff="@string/revanced_shorts_custom_actions_external_downloader_summary_off" />
|
||||||
|
<SwitchPreference android:title="@string/revanced_shorts_custom_actions_open_video_title" android:key="revanced_shorts_custom_actions_open_video" android:summaryOn="@string/revanced_shorts_custom_actions_open_video_summary_on" android:summaryOff="@string/revanced_shorts_custom_actions_open_video_summary_off" />
|
||||||
|
<SwitchPreference android:title="@string/revanced_shorts_custom_actions_repeat_state_title" android:key="revanced_shorts_custom_actions_repeat_state" android:summaryOn="@string/revanced_shorts_custom_actions_repeat_state_summary_on" android:summaryOff="@string/revanced_shorts_custom_actions_repeat_state_summary_off" />
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>SETTINGS: SHORTS_CUSTOM_ACTIONS -->
|
||||||
|
|
||||||
<!-- SETTINGS: SHORTS_TIME_STAMP
|
<!-- SETTINGS: SHORTS_TIME_STAMP
|
||||||
<SwitchPreference android:title="@string/revanced_enable_shorts_time_stamp_title" android:key="revanced_enable_shorts_time_stamp" android:summaryOn="@string/revanced_enable_shorts_time_stamp_summary_on" android:summaryOff="@string/revanced_enable_shorts_time_stamp_summary_off" />
|
<SwitchPreference android:title="@string/revanced_enable_shorts_time_stamp_title" android:key="revanced_enable_shorts_time_stamp" android:summaryOn="@string/revanced_enable_shorts_time_stamp_summary_on" android:summaryOff="@string/revanced_enable_shorts_time_stamp_summary_off" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user