feat(YouTube): Support version 19.34.42

This commit is contained in:
inotia00
2024-12-09 22:50:53 +09:00
parent 977b7f481b
commit 53c64552dd
31 changed files with 744 additions and 400 deletions

View File

@ -26,9 +26,4 @@ public class InitializationPatch {
showRestartDialog(mActivity, "revanced_extended_restart_first_run", 3000);
Utils.runOnMainThreadDelayed(() -> BaseSettings.SETTINGS_INITIALIZED.save(true), 3000);
}
public static void setDeviceInformation(@NonNull Activity mActivity) {
ExtendedUtils.setApplicationLabel();
ExtendedUtils.setVersionName();
}
}

View File

@ -248,8 +248,8 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
@SuppressLint("SimpleDateFormat")
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
var appName = ExtendedUtils.getApplicationLabel();
var versionName = ExtendedUtils.getVersionName();
var appName = ExtendedUtils.getAppLabel();
var versionName = ExtendedUtils.getAppVersionName();
var formatDate = dateFormat.format(new Date(System.currentTimeMillis()));
var fileName = String.format("%s_v%s_%s.txt", appName, versionName, formatDate);

View File

@ -8,16 +8,25 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class PackageUtils extends Utils {
private static String applicationLabel = "";
private static int smallestScreenWidthDp = 0;
private static String versionName = "";
public static String getApplicationLabel() {
return applicationLabel;
public static String getAppLabel() {
final PackageInfo packageInfo = getPackageInfo();
if (packageInfo != null) {
final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (applicationInfo != null && applicationInfo.loadLabel(getPackageManager()) instanceof String applicationLabel) {
return applicationLabel;
}
}
return "";
}
public static String getVersionName() {
return versionName;
public static String getAppVersionName() {
final PackageInfo packageInfo = getPackageInfo();
if (packageInfo != null) {
return packageInfo.versionName;
} else {
return "";
}
}
public static boolean isPackageEnabled(@NonNull String packageName) {
@ -30,32 +39,11 @@ public class PackageUtils extends Utils {
}
public static boolean isTablet() {
return smallestScreenWidthDp >= 600;
}
public static void setApplicationLabel() {
final PackageInfo packageInfo = getPackageInfo();
if (packageInfo != null) {
final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (applicationInfo != null) {
applicationLabel = (String) applicationInfo.loadLabel(getPackageManager());
}
}
}
public static void setSmallestScreenWidthDp() {
smallestScreenWidthDp = context.getResources().getConfiguration().smallestScreenWidthDp;
}
public static void setVersionName() {
final PackageInfo packageInfo = getPackageInfo();
if (packageInfo != null) {
versionName = packageInfo.versionName;
}
return getSmallestScreenWidthDp() >= 600;
}
public static int getSmallestScreenWidthDp() {
return smallestScreenWidthDp;
return context.getResources().getConfiguration().smallestScreenWidthDp;
}
// utils

View File

@ -78,8 +78,8 @@ public class FeedPatch {
);
}
public static boolean hideFloatingButton() {
return Settings.HIDE_FLOATING_BUTTON.get();
public static String hideFloatingButton(String fab) {
return Settings.HIDE_FLOATING_BUTTON.get() ? null : fab;
}
public static void hideLatestVideosButton(View view) {

View File

@ -1,15 +1,17 @@
package app.revanced.extension.youtube.patches.general;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.ORIGINAL;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.*;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_20_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_21_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_26_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_29_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.validateValue;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
@ -17,9 +19,10 @@ import androidx.annotation.Nullable;
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.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public final class MiniplayerPatch {
/**
@ -27,14 +30,28 @@ public final class MiniplayerPatch {
*/
public enum MiniplayerType {
/**
* Unmodified type, and same as un-patched.
* Disabled. When swiped down the miniplayer is immediately closed.
* Only available with 19.43+
*/
DISABLED(false, null),
/** Unmodified type, and same as un-patched. */
ORIGINAL(null, null),
/**
* Exactly the same as MINIMAL and only here for migration of user settings.
* Eventually this should be deleted.
*/
@Deprecated
PHONE(false, null),
MINIMAL(false, null),
TABLET(true, null),
MODERN_1(null, 1),
MODERN_2(null, 2),
MODERN_3(null, 3);
MODERN_3(null, 3),
/**
* Half broken miniplayer, that might be work in progress or left over abandoned code.
* Can force this type by editing the import/export settings.
*/
MODERN_4(null, 4);
/**
* Legacy tablet hook value.
@ -58,6 +75,44 @@ public final class MiniplayerPatch {
}
}
private static final int MINIPLAYER_SIZE;
static {
// YT appears to use the device screen dip width, plus an unknown fixed horizontal padding size.
DisplayMetrics displayMetrics = Utils.getContext().getResources().getDisplayMetrics();
final int deviceDipWidth = (int) (displayMetrics.widthPixels / displayMetrics.density);
// YT seems to use a minimum height to calculate the minimum miniplayer width based on the video.
// 170 seems to be the smallest that can be used and using less makes no difference.
final int WIDTH_DIP_MIN = 170; // Seems to be the smallest that works.
final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding.
// Round down to the nearest 5 pixels, to keep any error toasts easier to read.
final int WIDTH_DIP_MAX = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5);
Logger.printDebug(() -> "Screen dip width: " + deviceDipWidth + " maxWidth: " + WIDTH_DIP_MAX);
int dipWidth = Settings.MINIPLAYER_WIDTH_DIP.get();
if (dipWidth < WIDTH_DIP_MIN || dipWidth > WIDTH_DIP_MAX) {
Utils.showToastLong(str("revanced_miniplayer_width_dip_invalid_toast",
WIDTH_DIP_MIN, WIDTH_DIP_MAX));
// Instead of resetting, clamp the size at the bounds.
dipWidth = Math.max(WIDTH_DIP_MIN, Math.min(dipWidth, WIDTH_DIP_MAX));
Settings.MINIPLAYER_WIDTH_DIP.save(dipWidth);
}
MINIPLAYER_SIZE = dipWidth;
final int opacity = validateValue(
Settings.MINIPLAYER_OPACITY,
0,
100,
"revanced_miniplayer_opacity_invalid_toast"
);
OPACITY_LEVEL = (opacity * 255) / 100;
}
/**
* Modern subtitle overlay for {@link MiniplayerType#MODERN_2}.
* Resource is not present in older targets, and this field will be zero.
@ -67,19 +122,21 @@ public final class MiniplayerPatch {
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
/**
* Cannot turn off double tap with modern 2 or 3 with later targets,
* as forcing it off breakings tapping the miniplayer.
*/
private static final boolean DOUBLE_TAP_ACTION_ENABLED =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_2 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get();
// 19.29+ is very broken if double tap is not enabled.
IS_19_29_OR_GREATER ||
(CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get());
private static final boolean DRAG_AND_DROP_ENABLED =
CURRENT_TYPE == MODERN_1 && Settings.MINIPLAYER_DRAG_AND_DROP.get();
private static final boolean HIDE_EXPAND_CLOSE_AVAILABLE =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) &&
!DOUBLE_TAP_ACTION_ENABLED &&
!DRAG_AND_DROP_ENABLED;
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
private static final boolean HIDE_EXPAND_CLOSE_ENABLED =
HIDE_EXPAND_CLOSE_AVAILABLE && Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get();
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get()
&& Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable();
private static final boolean HIDE_SUBTEXT_ENABLED =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get();
@ -87,17 +144,49 @@ public final class MiniplayerPatch {
private static final boolean HIDE_REWIND_FORWARD_ENABLED =
CURRENT_TYPE == MODERN_1 && Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get();
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
Settings.MINIPLAYER_ROUNDED_CORNERS.get();
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
/**
* Remove a broken and always present subtitle text that is only
* present with {@link MiniplayerType#MODERN_2}. Bug was fixed in 19.21.
*/
private static final boolean HIDE_BROKEN_MODERN_2_SUBTITLE =
CURRENT_TYPE == MODERN_2 && !IS_19_21_OR_GREATER;
private static final int OPACITY_LEVEL;
static {
final int opacity = validateValue(
Settings.MINIPLAYER_OPACITY,
0,
100,
"revanced_miniplayer_opacity_invalid_toast"
);
public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return Settings.MINIPLAYER_TYPE.get().isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
}
}
OPACITY_LEVEL = (opacity * 255) / 100;
public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
return (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|| (!IS_19_26_OR_GREATER && type == MODERN_1
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|| (IS_19_29_OR_GREATER && type == MODERN_3);
}
}
/**
* Injection point.
* <p>
* Enables a handler that immediately closes the miniplayer when the video is minimized,
* effectively disabling the miniplayer.
*/
public static boolean getMiniplayerOnCloseHandler(boolean original) {
return CURRENT_TYPE == ORIGINAL
? original
: CURRENT_TYPE == DISABLED;
}
/**
@ -141,17 +230,71 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static boolean enableMiniplayerDoubleTapAction() {
public static boolean getModernFeatureFlagsActiveOverride(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
return original;
}
return CURRENT_TYPE.isModern();
}
/**
* Injection point.
*/
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
return original;
}
return DOUBLE_TAP_ACTION_ENABLED;
}
/**
* Injection point.
*/
public static boolean enableMiniplayerDragAndDrop() {
public static boolean enableMiniplayerDragAndDrop(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
return original;
}
return DRAG_AND_DROP_ENABLED;
}
/**
* Injection point.
*/
public static boolean setRoundedCorners(boolean original) {
if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
}
return original;
}
/**
* Injection point.
*/
public static int setMiniplayerDefaultSize(int original) {
if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_SIZE;
}
return original;
}
/**
* Injection point.
*/
public static boolean setHorizontalDrag(boolean original) {
if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
}
return original;
}
/**
* Injection point.
*/
@ -169,29 +312,36 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static boolean hideMiniplayerSubTexts(View view) {
// Different subviews are passed in, but only TextView and layouts are of interest here.
final boolean hideView = HIDE_SUBTEXT_ENABLED && (view instanceof TextView || view instanceof LinearLayout);
Utils.hideViewByRemovingFromParentUnderCondition(hideView, view);
return hideView || view == null;
public static void hideMiniplayerSubTexts(View view) {
try {
// Different subviews are passed in, but only TextView is of interest here.
if (HIDE_SUBTEXT_ENABLED && view instanceof TextView) {
Logger.printDebug(() -> "Hiding subtext view");
Utils.hideViewByRemovingFromParentUnderCondition(true, view);
}
} catch (Exception ex) {
Logger.printException(() -> "hideMiniplayerSubTexts failure", ex);
}
}
/**
* Injection point.
*/
public static void playerOverlayGroupCreated(View group) {
// Modern 2 has an half broken subtitle that is always present.
// Always hide it to make the miniplayer mostly usable.
if (CURRENT_TYPE == MODERN_2 && MODERN_OVERLAY_SUBTITLE_TEXT != 0) {
if (group instanceof ViewGroup viewGroup) {
View subtitleText = Utils.getChildView(viewGroup, true,
view -> view.getId() == MODERN_OVERLAY_SUBTITLE_TEXT);
try {
if (HIDE_BROKEN_MODERN_2_SUBTITLE && MODERN_OVERLAY_SUBTITLE_TEXT != 0) {
if (group instanceof ViewGroup) {
View subtitleText = Utils.getChildView((ViewGroup) group, true,
view -> view.getId() == MODERN_OVERLAY_SUBTITLE_TEXT);
if (subtitleText != null) {
subtitleText.setVisibility(View.GONE);
Logger.printDebug(() -> "Modern overlay subtitle view set to hidden");
if (subtitleText != null) {
subtitleText.setVisibility(View.GONE);
Logger.printDebug(() -> "Modern overlay subtitle view set to hidden");
}
}
}
} catch (Exception ex) {
Logger.printException(() -> "playerOverlayGroupCreated failure", ex);
}
}
}

View File

@ -31,9 +31,6 @@ public class InitializationPatch {
}
public static void setExtendedUtils(@NonNull Activity mActivity) {
ExtendedUtils.setApplicationLabel();
ExtendedUtils.setSmallestScreenWidthDp();
ExtendedUtils.setVersionName();
ExtendedUtils.setPlayerFlyoutMenuAdditionalSettings();
}
}

View File

@ -10,6 +10,7 @@ import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.Min
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
@ -34,6 +35,7 @@ import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeT
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch;
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch.StartPage;
import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch.FormFactor;
import app.revanced.extension.youtube.patches.general.MiniplayerPatch;
import app.revanced.extension.youtube.patches.general.YouTubeMusicActionsPatch;
import app.revanced.extension.youtube.patches.misc.SpoofStreamingDataPatch;
import app.revanced.extension.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType;
@ -167,11 +169,15 @@ public class Settings extends BaseSettings {
// PreferenceScreen: General - Miniplayer
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.ORIGINAL, true);
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_enable_double_tap_action", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3));
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_enable_drag_and_drop", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1));
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true);
private static final Setting.Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerPatch.MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerPatch.MiniplayerHideExpandCloseAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1));
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
// PreferenceScreen: General - Navigation bar

View File

@ -593,8 +593,8 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
private void exportActivity() {
@SuppressLint("SimpleDateFormat") final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
final String appName = ExtendedUtils.getApplicationLabel();
final String versionName = ExtendedUtils.getVersionName();
final String appName = ExtendedUtils.getAppLabel();
final String versionName = ExtendedUtils.getAppVersionName();
final String formatDate = dateFormat.format(new Date(System.currentTimeMillis()));
final String fileName = String.format("%s_v%s_%s.txt", appName, versionName, formatDate);

View File

@ -12,6 +12,12 @@ import app.revanced.extension.shared.utils.PackageUtils;
import app.revanced.extension.youtube.settings.Settings;
public class ExtendedUtils extends PackageUtils {
public static final boolean IS_19_17_OR_GREATER = getAppVersionName().compareTo("19.17.00") >= 0;
public static final boolean IS_19_20_OR_GREATER = getAppVersionName().compareTo("19.20.00") >= 0;
public static final boolean IS_19_21_OR_GREATER = getAppVersionName().compareTo("19.21.00") >= 0;
public static final boolean IS_19_26_OR_GREATER = getAppVersionName().compareTo("19.26.00") >= 0;
public static final boolean IS_19_29_OR_GREATER = getAppVersionName().compareTo("19.29.00") >= 0;
public static final boolean IS_19_34_OR_GREATER = getAppVersionName().compareTo("19.34.00") >= 0;
public static int validateValue(IntegerSetting settings, int min, int max, String message) {
int value = settings.get();

View File

@ -14,6 +14,7 @@ import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyResources
import app.revanced.util.getAdaptiveIconResourceFile
import app.revanced.util.getResourceGroup
import app.revanced.util.underBarOrThrow
import org.w3c.dom.Element
@ -248,38 +249,23 @@ val customBrandingIconPatch = resourcePatch(
return@execute
}
fun getAdaptiveIconResourceFile(tag: String): String {
document("res/mipmap-anydpi/ic_launcher_release.xml").use { document ->
val adaptiveIcon = document
.getElementsByTagName("adaptive-icon")
.item(0) as Element
val childNodes = adaptiveIcon.childNodes
for (i in 0 until childNodes.length) {
val node = childNodes.item(i)
if (node is Element && node.tagName == tag && node.hasAttribute("android:drawable")) {
return node.getAttribute("android:drawable").split("/")[1]
}
}
throw PatchException("Element not found: $tag")
}
}
mapOf(
ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("background"),
ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("foreground")
ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher_release.xml", "background"),
ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher_release.xml", "foreground")
).forEach { (oldIconResourceFile, newIconResourceFile) ->
mipmapDirectories.forEach {
val mipmapDirectory = resourceDirectory.resolve(it)
Files.move(
mipmapDirectory
.resolve("$oldIconResourceFile.png")
.toPath(),
mipmapDirectory
.resolve("$newIconResourceFile.png")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
if (oldIconResourceFile != newIconResourceFile) {
mipmapDirectories.forEach {
val mipmapDirectory = resourceDirectory.resolve(it)
Files.move(
mipmapDirectory
.resolve("$oldIconResourceFile.png")
.toPath(),
mipmapDirectory
.resolve("$newIconResourceFile.png")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
}

View File

@ -112,10 +112,6 @@ private val settingsBytecodePatch = bytecodePatch(
// endregion
injectOnCreateMethodCall(
EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR,
"setDeviceInformation"
)
injectOnCreateMethodCall(
EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR,
"onCreate"

View File

@ -147,18 +147,22 @@ val feedComponentsPatch = bytecodePatch(
// region patch for hide floating button
onCreateMethod.apply {
val fabIndex = indexOfFirstInstructionOrThrow {
val stringIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.CONST_STRING &&
getReference<StringReference>()?.string == "fab"
}
val fabRegister = getInstruction<OneRegisterInstruction>(fabIndex).registerA
val jumpIndex = indexOfFirstInstructionOrThrow(fabIndex + 1, Opcode.CONST_STRING)
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
val insertIndex = indexOfFirstInstructionOrThrow(stringIndex) {
opcode == Opcode.INVOKE_DIRECT &&
getReference<MethodReference>()?.name == "<init>"
}
val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex, Opcode.CONST_STRING)
addInstructionsWithLabels(
fabIndex, """
invoke-static {}, $FEED_CLASS_DESCRIPTOR->hideFloatingButton()Z
move-result v$fabRegister
if-nez v$fabRegister, :hide
insertIndex, """
invoke-static {v$stringRegister}, $FEED_CLASS_DESCRIPTOR->hideFloatingButton(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$stringRegister
if-eqz v$stringRegister, :hide
""", ExternalLabel("hide", getInstruction(jumpIndex))
)
}

View File

@ -2,20 +2,18 @@
package app.revanced.patches.youtube.general.miniplayer
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.utils.resourceid.floatyBarTopMargin
import app.revanced.patches.youtube.utils.resourceid.miniplayerMaxSize
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerForwardButton
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerRewindButton
import app.revanced.patches.youtube.utils.resourceid.scrimOverlay
import app.revanced.patches.youtube.utils.resourceid.ytOutlinePictureInPictureWhite
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.fingerprint.legacyFingerprint
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.util.MethodUtil
internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint(
name = "miniplayerDimensionsCalculatorParentFingerprint",
@ -25,6 +23,9 @@ internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint
literals = listOf(floatyBarTopMargin),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernAddViewListenerFingerprint = legacyFingerprint(
name = "miniplayerModernAddViewListenerFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
@ -32,6 +33,9 @@ internal val miniplayerModernAddViewListenerFingerprint = legacyFingerprint(
parameters = listOf("Landroid/view/View;")
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernCloseButtonFingerprint = legacyFingerprint(
name = "miniplayerModernCloseButtonFingerprint",
returnType = "Landroid/widget/ImageView;",
@ -40,41 +44,33 @@ internal val miniplayerModernCloseButtonFingerprint = legacyFingerprint(
literals = listOf(modernMiniPlayerClose),
)
private var constructorMethodCount = 0
internal fun isMultiConstructorMethod() = constructorMethodCount > 1
internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L
// In later targets this feature flag does nothing and is dead code.
internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L
internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L
internal const val MINIPLAYER_DRAG_DROP_FEATURE_KEY = 45628752L
internal const val MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY = 45658112L
internal const val MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY = 45652224L
internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L
internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L
internal val miniplayerModernConstructorFingerprint = legacyFingerprint(
name = "miniplayerModernConstructorFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf("L"),
literals = listOf(45623000L),
customFingerprint = custom@{ method, classDef ->
classDef.methods.forEach {
if (MethodUtil.isConstructor(it)) constructorMethodCount += 1
}
if (!is_19_25_or_greater)
return@custom true
// Double tap action (Used in YouTube 19.25.39+).
method.containsLiteralInstruction(45628823L)
&& method.containsLiteralInstruction(45630429L)
}
)
internal val miniplayerModernDragAndDropFingerprint = legacyFingerprint(
name = "miniplayerModernDragAndDropFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf("L"),
literals = listOf(45628752L),
)
internal val miniplayerModernEnabledFingerprint = legacyFingerprint(
name = "miniplayerModernEnabledFingerprint",
literals = listOf(45622882L),
internal val miniplayerOnCloseHandlerFingerprint = legacyFingerprint(
name = "miniplayerOnCloseHandlerFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z",
literals = listOf(MINIPLAYER_DISABLED_FEATURE_KEY),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernExpandButtonFingerprint = legacyFingerprint(
name = "miniplayerModernExpandButtonFingerprint",
returnType = "Landroid/widget/ImageView;",
@ -83,6 +79,9 @@ internal val miniplayerModernExpandButtonFingerprint = legacyFingerprint(
literals = listOf(modernMiniPlayerExpand),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernExpandCloseDrawablesFingerprint = legacyFingerprint(
name = "miniplayerModernExpandCloseDrawablesFingerprint",
returnType = "V",
@ -91,6 +90,9 @@ internal val miniplayerModernExpandCloseDrawablesFingerprint = legacyFingerprint
literals = listOf(ytOutlinePictureInPictureWhite),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint(
name = "miniplayerModernForwardButtonFingerprint",
returnType = "Landroid/widget/ImageView;",
@ -99,6 +101,9 @@ internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint(
literals = listOf(modernMiniPlayerForwardButton),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint(
name = "miniplayerModernOverlayViewFingerprint",
returnType = "V",
@ -107,6 +112,9 @@ internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint(
literals = listOf(scrimOverlay),
)
/**
* Matches using the class found in [miniplayerModernViewParentFingerprint].
*/
internal val miniplayerModernRewindButtonFingerprint = legacyFingerprint(
name = "miniplayerModernRewindButtonFingerprint",
returnType = "Landroid/widget/ImageView;",
@ -123,11 +131,16 @@ internal val miniplayerModernViewParentFingerprint = legacyFingerprint(
strings = listOf("player_overlay_modern_mini_player_controls")
)
internal val miniplayerMinimumSizeFingerprint = legacyFingerprint(
name = "miniplayerMinimumSizeFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(192L, 128L, miniplayerMaxSize),
)
internal val miniplayerOverrideFingerprint = legacyFingerprint(
name = "miniplayerOverrideFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
strings = listOf("appName")
)
@ -144,6 +157,7 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint(
returnType = "L",
parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"),
opcodes = listOf(
Opcode.RETURN_OBJECT,
Opcode.CHECK_CAST,
Opcode.CHECK_CAST,
Opcode.INVOKE_STATIC,
@ -152,12 +166,12 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint(
)
)
internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME =
"Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;"
internal val youTubePlayerOverlaysLayoutFingerprint = legacyFingerprint(
name = "youTubePlayerOverlaysLayoutFingerprint",
customFingerprint = { _, classDef ->
classDef.type == YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME
}
)
internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME =
"Lcom/google/android/apps/youtube/app/common/player/overlay/YouTubePlayerOverlaysLayout;"

View File

@ -1,22 +1,22 @@
package app.revanced.patches.youtube.general.miniplayer
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.youtube.utils.patch.PatchList.MINIPLAYER
import app.revanced.patches.youtube.utils.playservice.is_19_15_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_26_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_29_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_36_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_43_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose
import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand
@ -28,6 +28,7 @@ import app.revanced.patches.youtube.utils.resourceid.ytOutlinePictureInPictureWh
import app.revanced.patches.youtube.utils.resourceid.ytOutlineXWhite
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
@ -41,14 +42,13 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
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.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import com.android.tools.smali.dexlib2.util.MethodUtil
private const val EXTENSION_CLASS_DESCRIPTOR =
"$GENERAL_PATH/MiniplayerPatch;"
@ -83,7 +83,7 @@ val miniplayerPatch = bytecodePatch(
"""
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->$methodName(Z)Z
move-result v$register
"""
"""
)
}
@ -105,39 +105,34 @@ val miniplayerPatch = bytecodePatch(
* Adds an override to specify which modern miniplayer is used.
*/
fun MutableMethod.insertModernMiniplayerTypeOverride(iPutIndex: Int) {
val targetInstruction = getInstruction<TwoRegisterInstruction>(iPutIndex)
val targetReference = (targetInstruction as ReferenceInstruction).reference
val register = getInstruction<TwoRegisterInstruction>(iPutIndex).registerA
addInstructions(
iPutIndex + 1, """
invoke-static { v${targetInstruction.registerA} }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I
move-result v${targetInstruction.registerA}
# Original instruction
iput v${targetInstruction.registerA}, v${targetInstruction.registerB}, $targetReference
"""
addInstructionsAtControlFlowLabel(
iPutIndex,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I
move-result v$register
""",
)
removeInstruction(iPutIndex)
}
fun Pair<String, Fingerprint>.hookInflatedView(
fun MutableMethod.hookInflatedView(
literalValue: Long,
hookedClassType: String,
extensionMethodName: String,
) {
methodOrThrow(miniplayerModernViewParentFingerprint).apply {
val imageViewIndex = indexOfFirstInstructionOrThrow(
indexOfFirstLiteralInstructionOrThrow(literalValue)
) {
opcode == Opcode.CHECK_CAST &&
getReference<TypeReference>()?.type == hookedClassType
}
val register = getInstruction<OneRegisterInstruction>(imageViewIndex).registerA
addInstruction(
imageViewIndex + 1,
"invoke-static { v$register }, $extensionMethodName"
)
val imageViewIndex = indexOfFirstInstructionOrThrow(
indexOfFirstLiteralInstructionOrThrow(literalValue)
) {
opcode == Opcode.CHECK_CAST &&
getReference<TypeReference>()?.type == hookedClassType
}
val register = getInstruction<OneRegisterInstruction>(imageViewIndex).registerA
addInstruction(
imageViewIndex + 1,
"invoke-static { v$register }, $extensionMethodName"
)
}
// Modern mini player is only present and functional in 19.15+.
@ -184,7 +179,7 @@ val miniplayerPatch = bytecodePatch(
}
if (isPatchingOldVersion) {
settingArray += "SETTINGS: MINIPLAYER_TYPE_LEGACY"
settingArray += "SETTINGS: MINIPLAYER_TYPE_19_14"
addPreference(settingArray, MINIPLAYER)
// Return here, as patch below is only intended for new versions of the app.
@ -197,14 +192,14 @@ val miniplayerPatch = bytecodePatch(
miniplayerModernConstructorFingerprint.mutableClassOrThrow().methods.forEach {
it.apply {
if (MethodUtil.isConstructor(it)) {
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
val iPutIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT &&
getReference<FieldReference>()?.type == "I"
this.opcode == Opcode.IPUT &&
this.getReference<FieldReference>()?.type == "I"
}
insertModernMiniplayerTypeOverride(iPutIndex)
} else if (isMultiConstructorMethod()) {
} else {
findReturnIndicesReversed().forEach { index ->
insertModernMiniplayerOverride(
index
@ -214,27 +209,96 @@ val miniplayerPatch = bytecodePatch(
}
}
if (is_19_25_or_greater) {
miniplayerModernEnabledFingerprint.injectLiteralInstructionBooleanCall(
45622882L,
"$EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z"
if (is_19_23_or_greater) {
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_DRAG_DROP_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop(Z)Z"
)
settingArray += "SETTINGS: MINIPLAYER_DRAG_AND_DROP"
}
// endregion
// region Enable double tap action.
if (is_19_25_or_greater) {
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
45628823L,
"$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction()Z"
)
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
45630429L,
MINIPLAYER_MODERN_FEATURE_LEGACY_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverride(Z)Z"
)
settingArray += "SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION"
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_MODERN_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->getModernFeatureFlagsActiveOverride(Z)Z"
)
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_DOUBLE_TAP_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction(Z)Z"
)
if (!is_19_29_or_greater) {
settingArray += "SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION"
}
}
if (is_19_26_or_greater) {
miniplayerModernConstructorFingerprint.methodOrThrow().apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
MINIPLAYER_INITIAL_SIZE_FEATURE_KEY,
)
val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT)
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setMiniplayerDefaultSize(I)I
move-result v$register
""",
)
}
// Override a minimum size constant.
miniplayerMinimumSizeFingerprint.methodOrThrow().apply {
val index = indexOfFirstInstructionOrThrow {
opcode == Opcode.CONST_16 &&
(this as NarrowLiteralInstruction).narrowLiteral == 192
}
val register = getInstruction<OneRegisterInstruction>(index).registerA
// Smaller sizes can be used, but the miniplayer will always start in size 170 if set any smaller.
// The 170 initial limit probably could be patched to allow even smaller initial sizes,
// but 170 is already half the horizontal space and smaller does not seem useful.
replaceInstruction(index, "const/16 v$register, 170")
}
settingArray += "SETTINGS: MINIPLAYER_WIDTH_DIP"
} else {
settingArray += "SETTINGS: MINIPLAYER_REWIND_FORWARD"
}
if (is_19_36_or_greater) {
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z"
)
settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CONERS"
}
if (is_19_43_or_greater) {
miniplayerOnCloseHandlerFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_DISABLED_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->getMiniplayerOnCloseHandler(Z)Z"
)
miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall(
MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY,
"$EXTENSION_CLASS_DESCRIPTOR->setHorizontalDrag(Z)Z"
)
settingArray += "SETTINGS: MINIPLAYER_HORIZONTAL_DRAG"
settingArray += "SETTINGS: MINIPLAYER_TYPE_19_43"
} else {
settingArray += "SETTINGS: MINIPLAYER_TYPE_19_16"
}
// endregion
@ -291,7 +355,7 @@ val miniplayerPatch = bytecodePatch(
"adjustMiniplayerOpacity"
)
).forEach { (fingerprint, literalValue, methodName) ->
fingerprint.hookInflatedView(
fingerprint.methodOrThrow(miniplayerModernViewParentFingerprint).hookInflatedView(
literalValue,
"Landroid/widget/ImageView;",
"$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/widget/ImageView;)V"
@ -300,23 +364,19 @@ val miniplayerPatch = bytecodePatch(
miniplayerModernAddViewListenerFingerprint.methodOrThrow(
miniplayerModernViewParentFingerprint
).apply {
addInstructionsWithLabels(
0,
"""
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->hideMiniplayerSubTexts(Landroid/view/View;)Z
move-result v0
if-nez v0, :hidden
""",
ExternalLabel("hidden", getInstruction(implementation!!.instructions.lastIndex))
)
}
).addInstruction(
0,
"invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"hideMiniplayerSubTexts(Landroid/view/View;)V",
)
// Modern 2 has a broken overlay subtitle view that is always present.
// Modern 2 uses the same overlay controls as the regular video player,
// and the overlay views are added at runtime.
// Add a hook to the overlay class, and pass the added views to extension.
//
// NOTE: Modern 2 uses the same video UI as the regular player except resized to smaller.
// This patch code could be used to hide other player overlays that do not use Litho.
youTubePlayerOverlaysLayoutFingerprint.matchOrThrow().let {
it.method.apply {
it.classDef.methods.add(
@ -352,18 +412,6 @@ val miniplayerPatch = bytecodePatch(
// endregion
// region Enable drag and drop.
if (is_19_23_or_greater) {
miniplayerModernDragAndDropFingerprint.injectLiteralInstructionBooleanCall(
45628752L,
"$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop()Z"
)
settingArray += "SETTINGS: MINIPLAYER_DRAG_AND_DROP"
}
// endregion
settingArray += "SETTINGS: MINIPLAYER_TYPE_MODERN"
// region add settings

View File

@ -1,17 +1,69 @@
package app.revanced.patches.youtube.general.spoofappversion
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_APP_VERSION
import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater
import app.revanced.patches.youtube.utils.playservice.is_18_39_or_greater
import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater
import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.patches.youtube.utils.toolBarButtonFingerprint
import app.revanced.util.appendAppVersion
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private val spoofAppVersionBytecodePatch = bytecodePatch(
description = "spoofAppVersionBytecodePatch"
) {
dependsOn(versionCheckPatch)
execute {
if (!is_19_23_or_greater) {
return@execute
}
/**
* When spoofing the app version to YouTube 19.20.xx or earlier via Spoof app version on YouTube 19.23.xx+, the Library tab will crash.
* As a temporary workaround, do not set an image in the toolbar when the enum name is UNKNOWN.
*/
toolBarButtonFingerprint.methodOrThrow().apply {
val getDrawableIndex = indexOfGetDrawableInstruction(this)
val enumOrdinalIndex = indexOfFirstInstructionReversedOrThrow(getDrawableIndex) {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.returnType == "I"
}
val insertIndex = enumOrdinalIndex + 2
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setImageDrawable"
} + 1
addInstructionsWithLabels(
insertIndex, """
if-eqz v$insertRegister, :ignore
""", ExternalLabel("ignore", getInstruction(jumpIndex))
)
}
}
}
@Suppress("unused")
val spoofAppVersionPatch = resourcePatch(
@ -22,6 +74,7 @@ val spoofAppVersionPatch = resourcePatch(
dependsOn(
baseSpoofAppVersionPatch("$GENERAL_CLASS_DESCRIPTOR->getVersionOverride(Ljava/lang/String;)Ljava/lang/String;"),
spoofAppVersionBytecodePatch,
settingsPatch,
versionCheckPatch,
)

View File

@ -297,6 +297,7 @@ val toolBarComponentsPatch = bytecodePatch(
// endregion
/*
// region patch for hide voice search button
if (is_19_28_or_greater) {
@ -311,6 +312,7 @@ val toolBarComponentsPatch = bytecodePatch(
}
// endregion
*/
// region patch for hide voice search button

View File

@ -5,6 +5,8 @@ import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.patch.PatchList.CUSTOM_BRANDING_ICON_FOR_YOUTUBE
import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.utils.playservice.versionCheckPatch
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusIcon
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
@ -12,9 +14,16 @@ import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyFile
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.getAdaptiveIconResourceFile
import app.revanced.util.getResourceGroup
import app.revanced.util.underBarOrThrow
import java.nio.file.Files
import java.nio.file.StandardCopyOption
private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME =
"adaptiveproduct_youtube_background_color_108"
private const val ADAPTIVE_ICON_FOREGROUND_FILE_NAME =
"adaptiveproduct_youtube_foreground_color_108"
private const val DEFAULT_ICON = "revancify_blue"
private val availableIcon = mapOf(
@ -39,8 +48,8 @@ private val drawableDirectories = sizeArray.map { "drawable-$it" }
private val mipmapDirectories = sizeArray.map { "mipmap-$it" }
private val launcherIconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_background_color_108",
"adaptiveproduct_youtube_foreground_color_108",
ADAPTIVE_ICON_BACKGROUND_FILE_NAME,
ADAPTIVE_ICON_FOREGROUND_FILE_NAME,
"ic_launcher",
"ic_launcher_round"
).map { "$it.png" }.toTypedArray()
@ -83,7 +92,10 @@ val customBrandingIconPatch = resourcePatch(
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
dependsOn(
settingsPatch,
versionCheckPatch,
)
val appIconOption = stringOption(
@ -181,5 +193,32 @@ val customBrandingIconPatch = resourcePatch(
updatePatchStatusIcon(appIcon)
}
if (!is_19_34_or_greater) {
return@execute
}
if (appIcon == "youtube") {
return@execute
}
mapOf(
ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher.xml", "background"),
ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("res/mipmap-anydpi/ic_launcher.xml", "foreground")
).forEach { (oldIconResourceFile, newIconResourceFile) ->
if (oldIconResourceFile != newIconResourceFile) {
mipmapDirectories.forEach {
val mipmapDirectory = get("res").resolve(it)
Files.copy(
mipmapDirectory
.resolve("$oldIconResourceFile.png")
.toPath(),
mipmapDirectory
.resolve("$newIconResourceFile.png")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
}
}
}

View File

@ -386,6 +386,8 @@ val playerComponentsPatch = bytecodePatch(
findMethodOrThrow(syntheticReference) {
name == "onClick"
}.hookInitVideoPanel(0)
} else {
println("WARNING: target Opcode not found in ${fingerprint.first}")
}
}
}

View File

@ -5,6 +5,7 @@ import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast
import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarColorizedBarPlayedColorDark
import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarPlayedNotHighlightedColor
import app.revanced.patches.youtube.utils.resourceid.insetOverlayViewLayout
import app.revanced.patches.youtube.utils.resourceid.menuItemView
import app.revanced.patches.youtube.utils.resourceid.scrimOverlay
import app.revanced.patches.youtube.utils.resourceid.seekUndoEduOverlayStub
import app.revanced.patches.youtube.utils.resourceid.totalTime
@ -12,9 +13,13 @@ import app.revanced.patches.youtube.utils.resourceid.varispeedUnavailableTitle
import app.revanced.patches.youtube.utils.resourceid.videoQualityBottomSheet
import app.revanced.patches.youtube.utils.sponsorblock.sponsorBlockBytecodePatch
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 engagementPanelBuilderFingerprint = legacyFingerprint(
name = "engagementPanelBuilderFingerprint",
@ -164,6 +169,23 @@ internal val seekbarOnDrawFingerprint = legacyFingerprint(
customFingerprint = { method, _ -> method.name == "onDraw" }
)
internal fun indexOfGetDrawableInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == "Landroid/content/res/Resources;->getDrawable(I)Landroid/graphics/drawable/Drawable;"
}
internal val toolBarButtonFingerprint = legacyFingerprint(
name = "toolBarButtonFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/MenuItem;"),
literals = listOf(menuItemView),
customFingerprint = { method, _ ->
indexOfGetDrawableInstruction(method) >= 0
}
)
internal val totalTimeFingerprint = legacyFingerprint(
name = "totalTimeFingerprint",
returnType = "V",

View File

@ -14,7 +14,8 @@ internal object Constants {
"18.38.44", // This is the last version with no delay in applying video quality on the server side.
"18.48.39", // This is the last version that do not use Rolling Number.
"19.05.36", // This is the last version with the least YouTube experimental flag.
"19.16.39", // This is the latest version supported by the RVX patch.
"19.16.39", // This is the last version where the 'Restore old seekbar thumbnails' setting works.
"19.34.42", // This is the latest version supported by the RVX patch.
)
)
}

View File

@ -134,8 +134,8 @@ private val playerControlsBytecodePatch = bytecodePatch(
changeVisibilityMethod =
findMethodOrThrow(EXTENSION_PLAYER_CONTROLS_CLASS_DESCRIPTOR) {
name == "changeVisibility"
&& parameters == listOf("Z", "Z")
name == "changeVisibility" &&
parameters == listOf("Z", "Z")
}
changeVisibilityNegatedImmediatelyMethod =

View File

@ -10,12 +10,6 @@ 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 fun indexOfLayoutDirectionInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Landroid/view/View;->setLayoutDirection(I)V"
}
internal val browseIdClassFingerprint = legacyFingerprint(
name = "browseIdClassFingerprint",
returnType = "Ljava/lang/Object;",
@ -45,18 +39,26 @@ internal val reelWatchPagerFingerprint = legacyFingerprint(
literals = listOf(reelWatchPlayer),
)
internal fun indexOfStringIsEmptyInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>().toString() == "Ljava/lang/String;->isEmpty()Z"
}
internal val searchQueryClassFingerprint = legacyFingerprint(
name = "searchQueryClassFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "Ljava/util/Map;"),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
),
strings = listOf("force_enable_sticky_browsy_bars"),
customFingerprint = { method, _ ->
indexOfStringIsEmptyInstruction(method) >= 0
}
)
internal val videoStateFingerprint = legacyFingerprint(

View File

@ -135,32 +135,30 @@ val playerTypeHookPatch = bytecodePatch(
// region patch for hook search bar
searchQueryClassFingerprint.matchOrThrow().let {
it.method.apply {
val searchQueryIndex = it.patternMatch!!.startIndex + 1
val searchQueryFieldReference = getInstruction<ReferenceInstruction>(searchQueryIndex).reference
val searchQueryClass = (searchQueryFieldReference as FieldReference).definingClass
searchQueryClassFingerprint.methodOrThrow().apply {
val searchQueryIndex = indexOfStringIsEmptyInstruction(this) - 1
val searchQueryFieldReference = getInstruction<ReferenceInstruction>(searchQueryIndex).reference
val searchQueryClass = (searchQueryFieldReference as FieldReference).definingClass
findMethodOrThrow(searchQueryClass).apply {
val smaliInstructions =
findMethodOrThrow(searchQueryClass).apply {
val smaliInstructions =
"""
if-eqz v0, :ignore
iget-object v0, v0, $searchQueryFieldReference
if-eqz v0, :ignore
return-object v0
:ignore
const-string v0, ""
return-object v0
"""
if-eqz v0, :ignore
iget-object v0, v0, $searchQueryFieldReference
if-eqz v0, :ignore
return-object v0
:ignore
const-string v0, ""
return-object v0
"""
addStaticFieldToExtension(
EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR,
"getSearchQuery",
"searchQueryClass",
definingClass,
smaliInstructions
)
}
addStaticFieldToExtension(
EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR,
"getSearchQuery",
"searchQueryClass",
definingClass,
smaliInstructions
)
}
}

View File

@ -23,10 +23,22 @@ var is_19_23_or_greater = false
private set
var is_19_25_or_greater = false
private set
var is_19_26_or_greater = false
private set
var is_19_28_or_greater = false
private set
var is_19_29_or_greater = false
private set
var is_19_32_or_greater = false
private set
var is_19_34_or_greater = false
private set
var is_19_36_or_greater = false
private set
var is_19_41_or_greater = false
private set
var is_19_43_or_greater = false
private set
var is_19_44_or_greater = false
private set
@ -53,8 +65,14 @@ val versionCheckPatch = resourcePatch(
is_19_15_or_greater = 241602000 <= playStoreServicesVersion
is_19_23_or_greater = 242402000 <= playStoreServicesVersion
is_19_25_or_greater = 242599000 <= playStoreServicesVersion
is_19_26_or_greater = 242705000 <= playStoreServicesVersion
is_19_28_or_greater = 242905000 <= playStoreServicesVersion
is_19_29_or_greater = 243005000 <= playStoreServicesVersion
is_19_32_or_greater = 243305000 <= playStoreServicesVersion
is_19_34_or_greater = 243499000 <= playStoreServicesVersion
is_19_36_or_greater = 243705000 <= playStoreServicesVersion
is_19_41_or_greater = 244305000 <= playStoreServicesVersion
is_19_43_or_greater = 244405000 <= playStoreServicesVersion
is_19_44_or_greater = 244505000 <= playStoreServicesVersion
}
}

View File

@ -120,6 +120,8 @@ var menuItemView = -1L
private set
var metaPanel = -1L
private set
var miniplayerMaxSize = -1L
private set
var modernMiniPlayerClose = -1L
private set
var modernMiniPlayerExpand = -1L
@ -444,6 +446,10 @@ internal val sharedResourceIdPatch = resourcePatch(
ID,
"metapanel"
]
miniplayerMaxSize = resourceMappings[
DIMEN,
"miniplayer_max_size",
]
modernMiniPlayerClose = resourceMappings[
ID,
"modern_miniplayer_close"

View File

@ -1,26 +1,10 @@
package app.revanced.patches.youtube.utils.toolbar
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.resourceid.menuItemView
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val toolBarButtonFingerprint = legacyFingerprint(
name = "toolBarButtonFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/MenuItem;"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL
),
literals = listOf(menuItemView),
)
internal val toolBarPatchFingerprint = legacyFingerprint(
name = "toolBarPatchFingerprint",
accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,

View File

@ -6,12 +6,18 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.youtube.utils.indexOfGetDrawableInstruction
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.patches.youtube.utils.toolBarButtonFingerprint
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
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.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/ToolBarPatch;"
@ -24,30 +30,35 @@ val toolBarHookPatch = bytecodePatch(
dependsOn(sharedResourceIdPatch)
execute {
toolBarButtonFingerprint.matchOrThrow().let {
it.method.apply {
val replaceIndex = it.patternMatch!!.startIndex
val freeIndex = it.patternMatch!!.endIndex - 1
val replaceReference = getInstruction<ReferenceInstruction>(replaceIndex).reference
val replaceRegister =
getInstruction<FiveRegisterInstruction>(replaceIndex).registerC
val enumRegister = getInstruction<FiveRegisterInstruction>(replaceIndex).registerD
val freeRegister = getInstruction<TwoRegisterInstruction>(freeIndex).registerA
val imageViewIndex = replaceIndex + 2
val imageViewReference =
getInstruction<ReferenceInstruction>(imageViewIndex).reference
addInstructions(
replaceIndex + 1, """
iget-object v$freeRegister, p0, $imageViewReference
invoke-static {v$enumRegister, v$freeRegister}, $EXTENSION_CLASS_DESCRIPTOR->hookToolBar(Ljava/lang/Enum;Landroid/widget/ImageView;)V
invoke-interface {v$replaceRegister, v$enumRegister}, $replaceReference
"""
)
removeInstruction(replaceIndex)
toolBarButtonFingerprint.methodOrThrow().apply {
val getDrawableIndex = indexOfGetDrawableInstruction(this)
val enumOrdinalIndex = indexOfFirstInstructionReversedOrThrow(getDrawableIndex) {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.returnType == "I"
}
val freeIndex = getDrawableIndex - 1
val replaceReference = getInstruction<ReferenceInstruction>(enumOrdinalIndex).reference
val replaceRegister =
getInstruction<FiveRegisterInstruction>(enumOrdinalIndex).registerC
val enumRegister = getInstruction<FiveRegisterInstruction>(enumOrdinalIndex).registerD
val freeRegister = getInstruction<TwoRegisterInstruction>(freeIndex).registerA
val imageViewIndex = indexOfFirstInstructionReversedOrThrow(enumOrdinalIndex) {
opcode == Opcode.IGET_OBJECT &&
getReference<FieldReference>()?.type == "Landroid/widget/ImageView;"
}
val imageViewReference =
getInstruction<ReferenceInstruction>(imageViewIndex).reference
addInstructions(
enumOrdinalIndex + 1, """
iget-object v$freeRegister, p0, $imageViewReference
invoke-static {v$enumRegister, v$freeRegister}, $EXTENSION_CLASS_DESCRIPTOR->hookToolBar(Ljava/lang/Enum;Landroid/widget/ImageView;)V
invoke-interface {v$replaceRegister, v$enumRegister}, $replaceReference
"""
)
removeInstruction(enumOrdinalIndex)
}
toolbarMethod = toolBarPatchFingerprint.methodOrThrow()

View File

@ -5,7 +5,6 @@ import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.util.Document
import org.w3c.dom.Attr
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
@ -44,26 +43,6 @@ fun Node.cloneNodes(parent: Node) {
parent.removeChild(this)
}
/**
* Returns a sequence for all child nodes.
*/
fun NodeList.asSequence() = (0 until this.length).asSequence().map { this.item(it) }
/**
* Returns a sequence for all child nodes.
*/
@Suppress("UNCHECKED_CAST")
fun Node.childElementsSequence() =
this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence<Element>
/**
* Performs the given [action] on each child element.
*/
inline fun Node.forEachChildElement(action: (Element) -> Unit) =
childElementsSequence().forEach {
action(it)
}
/**
* Recursively traverse the DOM tree starting from the given root node.
*
@ -74,20 +53,29 @@ fun Node.doRecursively(action: (Node) -> Unit) {
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
}
fun String.startsWithAny(vararg prefixes: String): Boolean {
for (prefix in prefixes)
if (this.startsWith(prefix))
return true
return false
}
fun List<String>.getResourceGroup(fileNames: Array<String>) = map { directory ->
ResourceGroup(
directory, *fileNames
)
}
fun ResourcePatchContext.getAdaptiveIconResourceFile(path: String, tag: String): String {
document(path).use { document ->
val adaptiveIcon = document
.getElementsByTagName("adaptive-icon")
.item(0) as Element
val childNodes = adaptiveIcon.childNodes
for (i in 0 until childNodes.length) {
val node = childNodes.item(i)
if (node is Element && node.tagName == tag && node.hasAttribute("android:drawable")) {
return node.getAttribute("android:drawable").split("/")[1]
}
}
throw PatchException("Element not found: $tag")
}
}
fun ResourcePatchContext.appendAppVersion(appVersion: String) {
addEntryValues(
"revanced_spoof_app_version_target_entries",
@ -227,14 +215,6 @@ fun ResourcePatchContext.removeStringsElements(
}
}
fun Node.insertFirst(node: Node) {
if (hasChildNodes()) {
insertBefore(node, firstChild)
} else {
appendChild(node)
}
}
fun Node.insertNode(tagName: String, targetNode: Node, block: Element.() -> Unit) {
val child = ownerDocument.createElement(tagName)
child.block()
@ -294,21 +274,6 @@ internal fun inputStreamFromBundledResource(
*/
class ResourceGroup(val resourceDirectoryName: String, vararg val resources: String)
/**
* Iterate through the children of a node by its tag.
* @param resource The xml resource.
* @param targetTag The target xml node.
* @param callback The callback to call when iterating over the nodes.
*/
fun ResourcePatchContext.iterateXmlNodeChildren(
resource: String,
targetTag: String,
callback: (node: Node) -> Unit,
) = document(classLoader.getResourceAsStream(resource)!!).use { document ->
val stringsNode = document.getElementsByTagName(targetTag).item(0).childNodes
for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
}
/**
* Copy resources from the current class loader to the resource directory.
* @param resourceDirectory The directory of the resource.
@ -383,12 +348,3 @@ internal fun NodeList.findElementByAttributeValue(attributeName: String, value:
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) =
findElementByAttributeValue(attributeName, value)
?: throw PatchException("Could not find: $attributeName $value")
internal fun Element.copyAttributesFrom(oldContainer: Element) {
// Copy attributes from the old element to the new element
val attributes = oldContainer.attributes
for (i in 0 until attributes.length) {
val attr = attributes.item(i) as Attr
setAttribute(attr.name, attr.value)
}
}

View File

@ -163,7 +163,8 @@
<item>MEMBERSHIPS_SHORTS_ONLY</item>
<item>MEMBERSHIPS_LIVESTREAMS_ONLY</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_15_entries">
<string-array name="revanced_miniplayer_type_19_43_entries">
<item>@string/revanced_miniplayer_type_entry_0</item>
<item>@string/revanced_miniplayer_type_entry_1</item>
<item>@string/revanced_miniplayer_type_entry_2</item>
<item>@string/revanced_miniplayer_type_entry_3</item>
@ -171,22 +172,39 @@
<item>@string/revanced_miniplayer_type_entry_5</item>
<item>@string/revanced_miniplayer_type_entry_6</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_15_entry_values">
<string-array name="revanced_miniplayer_type_19_43_entry_values">
<item>DISABLED</item>
<item>ORIGINAL</item>
<item>PHONE</item>
<item>MINIMAL</item>
<item>TABLET</item>
<item>MODERN_1</item>
<item>MODERN_2</item>
<item>MODERN_3</item>
</string-array>
<string-array name="revanced_miniplayer_type_legacy_entries">
<string-array name="revanced_miniplayer_type_19_16_entries">
<item>@string/revanced_miniplayer_type_entry_1</item>
<item>@string/revanced_miniplayer_type_entry_2</item>
<item>@string/revanced_miniplayer_type_entry_3</item>
<item>@string/revanced_miniplayer_type_entry_4</item>
<item>@string/revanced_miniplayer_type_entry_5</item>
<item>@string/revanced_miniplayer_type_entry_6</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_16_entry_values">
<item>ORIGINAL</item>
<item>MINIMAL</item>
<item>TABLET</item>
<item>MODERN_1</item>
<item>MODERN_2</item>
<item>MODERN_3</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_14_entries">
<item>@string/revanced_miniplayer_type_entry_1</item>
<item>@string/revanced_miniplayer_type_entry_2</item>
<item>@string/revanced_miniplayer_type_entry_3</item>
</string-array>
<string-array name="revanced_miniplayer_type_legacy_entry_values">
<string-array name="revanced_miniplayer_type_19_14_entry_values">
<item>ORIGINAL</item>
<item>PHONE</item>
<item>MINIMAL</item>
<item>TABLET</item>
</string-array>
<string-array name="revanced_return_youtube_username_display_format_entries">

View File

@ -449,30 +449,49 @@ Some components may not be hidden."</string>
<string name="revanced_preference_screen_miniplayer_summary">Change the style of the in app minimized player.</string>
<string name="revanced_miniplayer_type_title">Miniplayer type</string>
<string name="revanced_miniplayer_type_entry_0">Disabled</string>
<string name="revanced_miniplayer_type_entry_1">Original</string>
<string name="revanced_miniplayer_type_entry_2">Phone</string>
<string name="revanced_miniplayer_type_entry_2">Minimal</string>
<string name="revanced_miniplayer_type_entry_3">Tablet</string>
<string name="revanced_miniplayer_type_entry_4">Modern 1</string>
<string name="revanced_miniplayer_type_entry_5">Modern 2</string>
<string name="revanced_miniplayer_type_entry_6">Modern 3</string>
<string name="revanced_miniplayer_enable_double_tap_action_title">Enable double-tap action</string>
<string name="revanced_miniplayer_enable_double_tap_action_summary_on">"Double-tap action is enabled.
<string name="revanced_miniplayer_rounded_corners_title">Enable rounded corners</string>
<string name="revanced_miniplayer_rounded_corners_summary_on">Corners are rounded.</string>
<string name="revanced_miniplayer_rounded_corners_summary_off">Corners are square.</string>
<string name="revanced_miniplayer_double_tap_action_title">Enable double-tap and pinch to resize</string>
<string name="revanced_miniplayer_double_tap_action_summary_on">"Double-tap action and pinch to resize is enabled.
• Double-tap to change the minimized video to a larger size.
• Double-tap once more to change to the original size."</string>
<string name="revanced_miniplayer_enable_double_tap_action_summary_off">Double-tap action is disabled.</string>
<string name="revanced_miniplayer_enable_drag_and_drop_title">Enable drag and drop</string>
<string name="revanced_miniplayer_enable_drag_and_drop_summary_on">Drag and drop is enabled.</string>
<string name="revanced_miniplayer_enable_drag_and_drop_summary_off">Drag and drop is disabled.</string>
<string name="revanced_miniplayer_hide_expand_close_title">Hide expand and close buttons</string>
<string name="revanced_miniplayer_hide_expand_close_summary_on">Buttons are hidden.\n(swipe miniplayer to expand or close)</string>
<string name="revanced_miniplayer_hide_expand_close_summary_off">Expand and close buttons are shown.</string>
• Double tap to increase miniplayer size.
• Double tap again to restore original size."</string>
<string name="revanced_miniplayer_double_tap_action_summary_off">Double-tap action and pinch to resize is disabled.</string>
<string name="revanced_miniplayer_drag_and_drop_title">Enable drag and drop</string>
<string name="revanced_miniplayer_drag_and_drop_summary_on">"Drag and drop is enabled.
Miniplayer can be dragged to any corner of the screen."</string>
<string name="revanced_miniplayer_drag_and_drop_summary_off">Drag and drop is disabled.</string>
<string name="revanced_miniplayer_horizontal_drag_title">Enable horizontal drag gesture.</string>
<string name="revanced_miniplayer_horizontal_drag_summary_on">"Horizontal drag gesture enabled.
Miniplayer can be dragged off screen to the left or right."</string>
<string name="revanced_miniplayer_horizontal_drag_summary_off">Horizontal drag gesture disabled.</string>
<string name="revanced_miniplayer_hide_expand_close_title">Hide close button</string>
<string name="revanced_miniplayer_hide_expand_close_summary_on">Close button is hidden.</string>
<string name="revanced_miniplayer_hide_expand_close_summary_off">Close button is shown.</string>
<string name="revanced_miniplayer_hide_expand_close_legacy_title">Hide expand and close buttons</string>
<string name="revanced_miniplayer_hide_expand_close_legacy_summary_on">"Buttons are hidden.
Swipe to expand or close."</string>
<string name="revanced_miniplayer_hide_expand_close_legacy_summary_off">Expand and close buttons are shown.</string>
<string name="revanced_miniplayer_hide_subtext_title">Hide subtexts</string>
<string name="revanced_miniplayer_hide_subtext_summary_on">Subtexts are hidden.</string>
<string name="revanced_miniplayer_hide_subtext_summary_off">Subtexts are shown.</string>
<string name="revanced_miniplayer_hide_rewind_forward_title">Hide skip forward and back buttons</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_on">Skip forward and back are hidden.</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_off">Skip forward and back are shown.</string>
<string name="revanced_miniplayer_width_dip_title">Initial size</string>
<string name="revanced_miniplayer_width_dip_summary">Initial on screen size, in pixels.</string>
<string name="revanced_miniplayer_width_dip_invalid_toast">Pixel size must be between %1$s and %2$s.</string>
<string name="revanced_miniplayer_opacity_title">Overlay opacity</string>
<string name="revanced_miniplayer_opacity_summary">Opacity value between 0-100, where 0 is transparent.</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Miniplayer overlay opacity must be between 0-100.</string>

View File

@ -160,19 +160,42 @@
</PreferenceScreen>SETTINGS: HOOK_BUTTONS -->
<!-- SETTINGS: MINIPLAYER_TYPE_MODERN
<PreferenceScreen android:title="@string/revanced_preference_screen_miniplayer_title" android:key="revanced_preference_screen_miniplayer" android:summary="@string/revanced_preference_screen_miniplayer_summary">
<ListPreference android:entries="@array/revanced_miniplayer_type_19_15_entries" android:title="@string/revanced_miniplayer_type_title" android:key="revanced_miniplayer_type" android:entryValues="@array/revanced_miniplayer_type_19_15_entry_values" />SETTINGS: MINIPLAYER_TYPE_MODERN -->
<PreferenceScreen android:title="@string/revanced_preference_screen_miniplayer_title" android:key="revanced_preference_screen_miniplayer" android:summary="@string/revanced_preference_screen_miniplayer_summary">SETTINGS: MINIPLAYER_TYPE_MODERN -->
<!-- SETTINGS: MINIPLAYER_TYPE_19_16
<ListPreference android:entries="@array/revanced_miniplayer_type_19_16_entries" android:title="@string/revanced_miniplayer_type_title" android:key="revanced_miniplayer_type" android:entryValues="@array/revanced_miniplayer_type_19_16_entry_values" />SETTINGS: MINIPLAYER_TYPE_19_16 -->
<!-- SETTINGS: MINIPLAYER_TYPE_19_43
<ListPreference android:entries="@array/revanced_miniplayer_type_19_43_entries" android:title="@string/revanced_miniplayer_type_title" android:key="revanced_miniplayer_type" android:entryValues="@array/revanced_miniplayer_type_19_43_entry_values" />SETTINGS: MINIPLAYER_TYPE_19_43 -->
<!-- SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION
<SwitchPreference android:title="@string/revanced_miniplayer_enable_double_tap_action_title" android:key="revanced_miniplayer_enable_double_tap_action" android:summaryOn="@string/revanced_miniplayer_enable_double_tap_action_summary_on" android:summaryOff="@string/revanced_miniplayer_enable_double_tap_action_summary_off" />SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION -->
<SwitchPreference android:title="@string/revanced_miniplayer_double_tap_action_title" android:key="revanced_miniplayer_double_tap_action" android:summaryOn="@string/revanced_miniplayer_double_tap_action_summary_on" android:summaryOff="@string/revanced_miniplayer_double_tap_action_summary_off" />SETTINGS: MINIPLAYER_DOUBLE_TAP_ACTION -->
<!-- SETTINGS: MINIPLAYER_DRAG_AND_DROP
<SwitchPreference android:title="@string/revanced_miniplayer_enable_drag_and_drop_title" android:key="revanced_miniplayer_enable_drag_and_drop" android:summaryOn="@string/revanced_miniplayer_enable_drag_and_drop_summary_on" android:summaryOff="@string/revanced_miniplayer_enable_drag_and_drop_summary_off" />SETTINGS: MINIPLAYER_DRAG_AND_DROP -->
<SwitchPreference android:title="@string/revanced_miniplayer_drag_and_drop_title" android:key="revanced_miniplayer_drag_and_drop" android:summaryOn="@string/revanced_miniplayer_drag_and_drop_summary_on" android:summaryOff="@string/revanced_miniplayer_drag_and_drop_summary_off" />SETTINGS: MINIPLAYER_DRAG_AND_DROP -->
<!-- SETTINGS: MINIPLAYER_HORIZONTAL_DRAG
<SwitchPreference android:title="@string/revanced_miniplayer_horizontal_drag_title" android:key="revanced_miniplayer_horizontal_drag" android:summaryOn="@string/revanced_miniplayer_horizontal_drag_summary_on" android:summaryOff="@string/revanced_miniplayer_horizontal_drag_summary_off" />SETTINGS: MINIPLAYER_HORIZONTAL_DRAG -->
<!-- SETTINGS: MINIPLAYER_ROUNDED_CONERS
<SwitchPreference android:title="@string/revanced_miniplayer_rounded_corners_title" android:key="revanced_miniplayer_rounded_corners" android:summaryOn="@string/revanced_miniplayer_rounded_corners_summary_on" android:summaryOff="@string/revanced_miniplayer_rounded_corners_summary_off" />SETTINGS: MINIPLAYER_ROUNDED_CONERS -->
<!-- SETTINGS: MINIPLAYER_TYPE_MODERN
<SwitchPreference android:title="@string/revanced_miniplayer_hide_subtext_title" android:key="revanced_miniplayer_hide_subtext" android:summaryOn="@string/revanced_miniplayer_hide_subtext_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_subtext_summary_off" />SETTINGS: MINIPLAYER_TYPE_MODERN -->
<!-- SETTINGS: MINIPLAYER_EXPAND_CLOSE_19_25
<SwitchPreference android:title="@string/revanced_miniplayer_hide_expand_close_title" android:key="revanced_miniplayer_hide_expand_close" android:summaryOn="@string/revanced_miniplayer_hide_expand_close_legacy_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_expand_close_legacy_summary_off" />SETTINGS: MINIPLAYER_EXPAND_CLOSE_19_25 -->
<!-- SETTINGS: MINIPLAYER_EXPAND_CLOSE_19_26
<SwitchPreference android:title="@string/revanced_miniplayer_hide_expand_close_title" android:key="revanced_miniplayer_hide_expand_close" android:summaryOn="@string/revanced_miniplayer_hide_expand_close_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_expand_close_summary_off" />SETTINGS: MINIPLAYER_EXPAND_CLOSE_19_26 -->
<!-- SETTINGS: MINIPLAYER_REWIND_FORWARD
<SwitchPreference android:title="@string/revanced_miniplayer_hide_rewind_forward_title" android:key="revanced_miniplayer_hide_rewind_forward" android:summaryOn="@string/revanced_miniplayer_hide_rewind_forward_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_rewind_forward_summary_off" />SETTINGS: MINIPLAYER_REWIND_FORWARD -->
<!-- SETTINGS: MINIPLAYER_WIDTH_DIP
<app.revanced.extension.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_miniplayer_width_dip_title" android:key="revanced_miniplayer_width_dip" android:summary="@string/revanced_miniplayer_width_dip_summary" android:inputType="number" />SETTINGS: MINIPLAYER_WIDTH_DIP -->
<!-- SETTINGS: MINIPLAYER_TYPE_MODERN
<SwitchPreference android:title="@string/revanced_miniplayer_hide_expand_close_title" android:key="revanced_miniplayer_hide_expand_close" android:summaryOn="@string/revanced_miniplayer_hide_expand_close_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_expand_close_summary_off" />
<SwitchPreference android:title="@string/revanced_miniplayer_hide_subtext_title" android:key="revanced_miniplayer_hide_subtext" android:summaryOn="@string/revanced_miniplayer_hide_subtext_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_subtext_summary_off" />
<SwitchPreference android:title="@string/revanced_miniplayer_hide_rewind_forward_title" android:key="revanced_miniplayer_hide_rewind_forward" android:summaryOn="@string/revanced_miniplayer_hide_rewind_forward_summary_on" android:summaryOff="@string/revanced_miniplayer_hide_rewind_forward_summary_off" />
<app.revanced.extension.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_miniplayer_opacity_title" android:key="revanced_miniplayer_opacity" android:summary="@string/revanced_miniplayer_opacity_summary" android:inputType="number" />
</PreferenceScreen>SETTINGS: MINIPLAYER_TYPE_MODERN -->
@ -266,8 +289,8 @@
<SwitchPreference android:title="@string/revanced_hide_gray_separator_title" android:key="revanced_hide_gray_separator" android:summaryOn="@string/revanced_hide_gray_separator_summary_on" android:summaryOff="@string/revanced_hide_gray_separator_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_snack_bar_title" android:key="revanced_hide_snack_bar" android:summaryOn="@string/revanced_hide_snack_bar_summary_on" android:summaryOff="@string/revanced_hide_snack_bar_summary_off" />SETTINGS: HIDE_LAYOUT_COMPONENTS -->
<!-- SETTINGS: MINIPLAYER_TYPE_LEGACY
<ListPreference android:entries="@array/revanced_miniplayer_type_legacy_entries" android:title="@string/revanced_miniplayer_type_title" android:key="revanced_miniplayer_type" android:entryValues="@array/revanced_miniplayer_type_legacy_entry_values" />SETTINGS: MINIPLAYER_TYPE_LEGACY -->
<!-- SETTINGS: MINIPLAYER_TYPE_19_14
<ListPreference android:entries="@array/revanced_miniplayer_type_19_14_entries" android:title="@string/revanced_miniplayer_type_title" android:key="revanced_miniplayer_type" android:entryValues="@array/revanced_miniplayer_type_19_14_entry_values" />SETTINGS: MINIPLAYER_TYPE_19_14 -->
<!-- SETTINGS: REMOVE_VIEWER_DISCRETION_DIALOG
<SwitchPreference android:title="@string/revanced_remove_viewer_discretion_dialog_title" android:key="revanced_remove_viewer_discretion_dialog" android:summary="@string/revanced_remove_viewer_discretion_dialog_summary" />SETTINGS: REMOVE_VIEWER_DISCRETION_DIALOG -->