Skip to content

Commit

Permalink
feat(YouTube - Shorts components): Add Custom actions in toolbar se…
Browse files Browse the repository at this point in the history
…tting (YouTube 18.38.44+)
  • Loading branch information
Francesco146 authored and anddea committed Dec 17, 2024
1 parent ff5b527 commit 6732b2b
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
import static app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter.isShortsFlyoutMenuVisible;
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;

import android.app.AlertDialog;
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.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;

import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
import java.util.Map;
Expand All @@ -34,12 +38,83 @@ public final class CustomActionsPatch {
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 boolean SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED =
Settings.ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR.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 setToolbarMenu(String enumString, View toolbarView) {
if (!SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED) {
return;
}
if (ShortsPlayerState.getCurrent().isClosed()) {
return;
}
if (!isMoreButton(enumString)) {
return;
}
setToolbarMenuOnLongClickListener((ViewGroup) toolbarView);
}

private static void setToolbarMenuOnLongClickListener(ViewGroup parentView) {
ImageView imageView = Utils.getChildView(parentView, v -> v instanceof ImageView);
if (imageView == null) {
return;
}
Context context = imageView.getContext();
contextRef = new WeakReference<>(context);

// Overriding is possible only after OnClickListener is assigned to the more button.
Utils.runOnMainThreadDelayed(() -> imageView.setOnLongClickListener(button -> {
showMoreButtonDialog(context);
return true;
}), 0);
}

private static void showMoreButtonDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(getString("revanced_shorts_custom_actions_toolbar_dialog_title"));

Map<String, Runnable> toolbarMap = new LinkedHashMap<>(arrSize);

for (CustomAction customAction : CustomAction.values()) {
if (customAction.settings.get()) {
toolbarMap.putIfAbsent(customAction.getLabel(), customAction.getOnClickAction());
}
}

String[] titles = toolbarMap.keySet().toArray(new String[0]);
Runnable[] actions = toolbarMap.values().toArray(new Runnable[0]);
builder.setItems(titles, (dialog, which) -> {
String selectedOption = titles[which];
Runnable action = actions[which];
if (action != null) {
action.run();
} else {
Logger.printDebug(() -> "No action found for " + selectedOption);
}
});


AlertDialog dialog = builder.create();
dialog.show();
}

private static boolean isMoreButton(String enumString) {
return StringUtils.equalsAny(
enumString,
"MORE_VERT",
"MORE_VERT_BOLD"
);
}

/**
* Injection point.
*/
Expand Down Expand Up @@ -129,6 +204,24 @@ public static void onFlyoutMenuCreate(final RecyclerView recyclerView) {
});
}

/**
* Injection point.
*/
public static void onLiveHeaderElementsContainerCreate(final View view) {
if (!SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED) {
return;
}
view.getViewTreeObserver().addOnDrawListener(() -> {
try {
if (view instanceof ViewGroup viewGroup) {
setToolbarMenuOnLongClickListener(viewGroup);
}
} catch (Exception ex) {
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
}
});
}

private static void hideFlyoutMenu() {
if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,16 +473,17 @@ public class Settings extends BaseSettings {

// 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 ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR = new BooleanSetting("revanced_enable_shorts_custom_actions_toolbar", 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));
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU, ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR));
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));
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU, ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR));
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));
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU, ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR));
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));
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU, ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR));
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));
parentsAny(ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU, ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR));

// Experimental Flags
public static final BooleanSetting ENABLE_TIME_STAMP = new BooleanSetting("revanced_enable_shorts_time_stamp", FALSE, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ import app.revanced.patches.youtube.utils.resourceid.reelRightLikeIcon
import app.revanced.patches.youtube.utils.resourceid.reelVodTimeStampsContainer
import app.revanced.patches.youtube.utils.resourceid.rightComment
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference

internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint(
name = "bottomSheetMenuListBuilderFingerprint",
Expand All @@ -32,6 +36,22 @@ internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint(
strings = listOf("Bottom Sheet Menu is empty. No menu items were supported."),
)

internal val liveHeaderElementsContainerFingerprint = legacyFingerprint(
name = "liveHeaderElementsContainerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/ViewGroup;", "L"),
strings = listOf("Header container is null, header cannot be presented."),
customFingerprint = { method, _ ->
indexOfAddLiveHeaderElementsContainerInstruction(method) >= 0
},
)

fun indexOfAddLiveHeaderElementsContainerInstruction(method: Method) =
method.indexOfFirstInstruction {
getReference<MethodReference>()?.name == "addView"
}

internal val reelEnumConstructorFingerprint = legacyFingerprint(
name = "reelEnumConstructorFingerprint",
returnType = "V",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS
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_34_or_greater
import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_02_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.versionCheckPatch
Expand All @@ -53,6 +55,8 @@ 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.getContext
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.utils.toolbar.hookToolBar
import app.revanced.patches.youtube.utils.toolbar.toolBarHookPatch
import app.revanced.patches.youtube.video.information.hookShortsVideoInformation
import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
Expand Down Expand Up @@ -81,6 +85,7 @@ import app.revanced.util.or
import app.revanced.util.replaceLiteralInstructionCall
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
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.RegisterRangeInstruction
Expand Down Expand Up @@ -153,16 +158,50 @@ private val shortsCustomActionsPatch = bytecodePatch(
bottomSheetRecyclerViewPatch,
lithoFilterPatch,
playerTypeHookPatch,
toolBarHookPatch,
videoIdPatch,
videoInformationPatch,
versionCheckPatch,
)

execute {
if (!is_18_49_or_greater) {
if (!is_18_34_or_greater) {
return@execute
}

// region hook toolbar more button

hookToolBar("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->setToolbarMenu")

// toolbar in Shorts livestream
liveHeaderElementsContainerFingerprint.methodOrThrow().apply {
val addViewIndex = indexOfAddLiveHeaderElementsContainerInstruction(this)
val viewRegister = getInstruction<FiveRegisterInstruction>(addViewIndex).registerD

addInstruction(
addViewIndex + 1,
"invoke-static {v$viewRegister}, " +
"$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onLiveHeaderElementsContainerCreate(Landroid/view/View;)V"
)
}

// endregion

// region add litho filter

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)

// endregion

if (!is_19_02_or_greater) {
return@execute
}

// region hook flyout menu

bottomSheetMenuListBuilderFingerprint.matchOrThrow().let {
it.method.apply {
val addListIndex = indexOfFirstInstructionOrThrow {
Expand Down Expand Up @@ -280,10 +319,7 @@ private val shortsCustomActionsPatch = bytecodePatch(

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)
// endregion

}
}
Expand Down Expand Up @@ -571,8 +607,13 @@ val shortsComponentPatch = bytecodePatch(
settingArray += "SETTINGS: SHORTS_TIME_STAMP"
}

if (is_18_49_or_greater) {
settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS"
if (is_18_34_or_greater) {
settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS_SHARED"
settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS_TOOLBAR"
}

if (is_19_02_or_greater) {
settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU"
}

// region patch for hide comments button (non-litho)
Expand Down
18 changes: 14 additions & 4 deletions patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -584,16 +584,26 @@
<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" />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" />
<!-- SETTINGS: SHORTS_CUSTOM_ACTIONS_SHARED
<PreferenceCategory android:title="@string/revanced_preference_category_custom_actions" android:layout="@layout/revanced_settings_preferences_category"/>SETTINGS: SHORTS_CUSTOM_ACTIONS_SHARED -->

<!-- SETTINGS: SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU
<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" />SETTINGS: SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU -->

<!-- SETTINGS: SHORTS_CUSTOM_ACTIONS_TOOLBAR
<SwitchPreference android:title="@string/revanced_enable_shorts_custom_actions_toolbar_title" android:key="revanced_enable_shorts_custom_actions_toolbar" android:summaryOn="@string/revanced_enable_shorts_custom_actions_toolbar_summary_on" android:summaryOff="@string/revanced_enable_shorts_custom_actions_toolbar_summary_off" />SETTINGS: SHORTS_CUSTOM_ACTIONS_TOOLBAR -->

<!-- SETTINGS: SHORTS_CUSTOM_ACTIONS_SHARED
<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" />
<app.revanced.extension.youtube.settings.preference.ExternalDownloaderVideoPreference android:title="@string/revanced_external_downloader_package_name_video_title" android:key="revanced_external_downloader_package_name_video" android:summary="@string/revanced_external_downloader_package_name_video_summary" />
<Preference android:title="@string/revanced_shorts_custom_actions_about_title" android:selectable="false" android:summary="@string/revanced_shorts_custom_actions_about_about_summary" />SETTINGS: SHORTS_CUSTOM_ACTIONS_SHARED -->

<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>SETTINGS: SHORTS_CUSTOM_ACTIONS -->
<!-- SETTINGS: SHORTS_COMPONENTS
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>SETTINGS: SHORTS_COMPONENTS -->

<!-- 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" />
Expand Down

0 comments on commit 6732b2b

Please sign in to comment.