From d36ede0330b2dd446a9db5da494d8b3f3ab34848 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 4 Nov 2024 22:17:04 -0400 Subject: [PATCH] feat(YouTube - Miniplayer): Add horizontal drag gesture (#3859) --- .../youtube/patches/EnableDebuggingPatch.java | 38 ++++++++++++-- .../youtube/patches/MiniplayerPatch.java | 26 ++++++---- .../extension/youtube/settings/Settings.java | 2 + patches/api/patches.api | 19 ------- .../youtube/layout/miniplayer/Fingerprints.kt | 18 +++---- .../layout/miniplayer/MiniplayerPatch.kt | 52 +++++++++---------- .../youtube/layout/seekbar/Fingerprints.kt | 2 +- .../misc/debugging/EnableDebuggingPatch.kt | 46 ++++++++++++++-- .../youtube/misc/debugging/Fingerprints.kt | 15 +++++- .../youtube/misc/fix/cairo/Fingerprints.kt | 2 +- .../resources/addresources/values/strings.xml | 3 ++ 11 files changed, 149 insertions(+), 74 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java index 3154867421..60ffa53edb 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java @@ -10,12 +10,44 @@ public final class EnableDebuggingPatch { private static final ConcurrentMap featureFlags - = new ConcurrentHashMap<>(150, 0.75f, 1); + = new ConcurrentHashMap<>(300, 0.75f, 1); - public static boolean isFeatureFlagEnabled(long flag, boolean value) { + /** + * Injection point. + */ + public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) { if (value && BaseSettings.DEBUG.get()) { if (featureFlags.putIfAbsent(flag, true) == null) { - Logger.printDebug(() -> "feature is enabled: " + flag); + Logger.printDebug(() -> "boolean feature is enabled: " + flag); + } + } + + return value; + } + + /** + * Injection point. + */ + public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) { + if (defaultValue != value && BaseSettings.DEBUG.get()) { + if (featureFlags.putIfAbsent(flag, true) == null) { + // Align the log outputs to make post processing easier. + Logger.printDebug(() -> " double feature is enabled: " + flag + + " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue)); + } + } + + return value; + } + + /** + * Injection point. + */ + public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) { + if (defaultValue != value && BaseSettings.DEBUG.get()) { + if (featureFlags.putIfAbsent(flag, true) == null) { + Logger.printDebug(() -> " long feature is enabled: " + flag + + " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue)); } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/MiniplayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/MiniplayerPatch.java index e11cfffa81..33bdf26b29 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/MiniplayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/MiniplayerPatch.java @@ -122,6 +122,9 @@ public boolean isModern() { 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. @@ -131,6 +134,13 @@ public boolean isModern() { private static final int OPACITY_LEVEL; + public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return Settings.MINIPLAYER_TYPE.get().isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get(); + } + } + public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability { @Override public boolean isAvailable() { @@ -248,21 +258,15 @@ public static int setMiniplayerDefaultSize(int original) { return original; } - /** - * Injection point. - */ - public static float setMovementBoundFactor(float original) { - // Not clear if customizing this is useful or not. - // So for now just log this and use the original value. - if (original != 1.0) Logger.printDebug(() -> "setMovementBoundFactor original: " + original); - - return original; - } /** * Injection point. */ - public static boolean setDropShadow(boolean original) { + public static boolean setHorizontalDrag(boolean original) { + if (CURRENT_TYPE.isModern()) { + return MINIPLAYER_HORIZONTAL_DRAG_ENABLED; + } + return original; } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index ced98122b9..edcdecfa1b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -5,6 +5,7 @@ import static app.revanced.extension.shared.settings.Setting.*; import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability; +import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.*; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; @@ -138,6 +139,7 @@ public class Settings extends BaseSettings { private static final 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 MiniplayerHorizontalDragAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new 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)); diff --git a/patches/api/patches.api b/patches/api/patches.api index bf2c99b231..2e74b07c7e 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1133,17 +1133,6 @@ public final class app/revanced/patches/youtube/layout/hide/time/HideTimestampPa public static final fun getHideTimestampPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/youtube/layout/miniplayer/FingerprintsKt { - public static final field ANIMATION_INTERPOLATION_FEATURE_KEY J - public static final field DOUBLE_TAP_ENABLED_FEATURE_KEY_LITERAL J - public static final field DRAG_DROP_ENABLED_FEATURE_KEY_LITERAL J - public static final field DROP_SHADOW_FEATURE_KEY J - public static final field INITIAL_SIZE_FEATURE_KEY_LITERAL J - public static final field MODERN_FEATURE_FLAGS_ENABLED_KEY_LITERAL J - public static final field MODERN_MINIPLAYER_ENABLED_OLD_TARGETS_FEATURE_KEY J - public static final field ROUNDED_CORNERS_FEATURE_KEY J -} - public final class app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatchKt { public static final fun getFloatyBarButtonTopMargin ()J public static final fun getMiniplayerMaxSize ()J @@ -1188,10 +1177,6 @@ public final class app/revanced/patches/youtube/layout/searchbar/WideSearchbarPa public static final fun getWideSearchbarPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/youtube/layout/seekbar/FingerprintsKt { - public static final field PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG J -} - public final class app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatchKt { public static final fun getSeekbarColorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1274,10 +1259,6 @@ public final class app/revanced/patches/youtube/misc/extension/SharedExtensionPa public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/youtube/misc/fix/cairo/FingerprintsKt { - public static final field CAIRO_CONFIG_LITERAL_VALUE J -} - public final class app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatchKt { public static final fun getSpoofVideoStreamsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/Fingerprints.kt index aace8100f3..125f043a3b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/Fingerprints.kt @@ -1,3 +1,5 @@ +@file:Suppress("SpellCheckingInspection") + package app.revanced.patches.youtube.layout.miniplayer import app.revanced.patcher.fingerprint @@ -33,16 +35,14 @@ internal val miniplayerModernCloseButtonFingerprint = fingerprint { literal { modernMiniplayerClose } } -const val MODERN_FEATURE_FLAGS_ENABLED_KEY_LITERAL = 45622882L - +internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L // In later targets this feature flag does nothing and is dead code. -const val MODERN_MINIPLAYER_ENABLED_OLD_TARGETS_FEATURE_KEY = 45630429L -const val DOUBLE_TAP_ENABLED_FEATURE_KEY_LITERAL = 45628823L -const val DRAG_DROP_ENABLED_FEATURE_KEY_LITERAL = 45628752L -const val INITIAL_SIZE_FEATURE_KEY_LITERAL = 45640023L -const val ANIMATION_INTERPOLATION_FEATURE_KEY = 45647018L -const val DROP_SHADOW_FEATURE_KEY = 45652223L -const val ROUNDED_CORNERS_FEATURE_KEY = 45652224L +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 val miniplayerModernConstructorFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt index 1432caa070..e7b03d8f21 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt @@ -1,3 +1,5 @@ +@file:Suppress("SpellCheckingInspection") + package app.revanced.patches.youtube.layout.miniplayer import app.revanced.patcher.Match @@ -204,6 +206,10 @@ val miniplayerPatch = bytecodePatch( preferences += SwitchPreference("revanced_miniplayer_drag_and_drop") } + if (is_19_43_or_greater) { + preferences += SwitchPreference("revanced_miniplayer_horizontal_drag") + } + if (is_19_36_or_greater) { preferences += SwitchPreference("revanced_miniplayer_rounded_corners") } @@ -291,7 +297,7 @@ val miniplayerPatch = bytecodePatch( addInstructions( targetIndex + 1, """ - invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(F)F + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(F)F move-result v$register """, ) @@ -302,13 +308,13 @@ val miniplayerPatch = bytecodePatch( * Adds an override to specify which modern miniplayer is used. */ fun MutableMethod.insertModernMiniplayerTypeOverride(iPutIndex: Int) { - val targetInstruction = getInstruction(iPutIndex) + val register = getInstruction(iPutIndex).registerA addInstructionsAtControlFlowLabel( iPutIndex, """ - invoke-static { v${targetInstruction.registerA} }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I - move-result v${targetInstruction.registerA} + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getModernMiniplayerOverrideType(I)I + move-result v$register """, ) } @@ -378,24 +384,31 @@ val miniplayerPatch = bytecodePatch( if (is_19_23_or_greater) { miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - DRAG_DROP_ENABLED_FEATURE_KEY_LITERAL, + MINIPLAYER_DRAG_DROP_FEATURE_KEY, "enableMiniplayerDragAndDrop", ) } + if (is_19_43_or_greater) { + miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( + MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY, + "setHorizontalDrag", + ) + } + if (is_19_25_or_greater) { miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - MODERN_MINIPLAYER_ENABLED_OLD_TARGETS_FEATURE_KEY, + MINIPLAYER_MODERN_FEATURE_LEGACY_KEY, "getModernMiniplayerOverride", ) miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - MODERN_FEATURE_FLAGS_ENABLED_KEY_LITERAL, + MINIPLAYER_MODERN_FEATURE_KEY, "getModernFeatureFlagsActiveOverride", ) miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - DOUBLE_TAP_ENABLED_FEATURE_KEY_LITERAL, + MINIPLAYER_DOUBLE_TAP_FEATURE_KEY, "enableMiniplayerDoubleTapAction", ) } @@ -403,7 +416,7 @@ val miniplayerPatch = bytecodePatch( if (is_19_26_or_greater) { miniplayerModernConstructorMatch.mutableMethod.apply { val literalIndex = indexOfFirstLiteralInstructionOrThrow( - INITIAL_SIZE_FEATURE_KEY_LITERAL, + MINIPLAYER_INITIAL_SIZE_FEATURE_KEY, ) val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT) @@ -418,7 +431,7 @@ val miniplayerPatch = bytecodePatch( ) } - // Override a mininimum miniplayer size constant. + // Override a minimum size constant. miniplayerMinimumSizeMatch.mutableMethod.apply { val index = indexOfFirstInstructionOrThrow { opcode == Opcode.CONST_16 && (this as NarrowLiteralInstruction).narrowLiteral == 192 @@ -432,22 +445,9 @@ val miniplayerPatch = bytecodePatch( } } - if (is_19_32_or_greater) { - // Feature is not exposed in the settings, and currently only for debugging. - miniplayerModernConstructorMatch.insertLiteralValueFloatOverride( - ANIMATION_INTERPOLATION_FEATURE_KEY, - "setMovementBoundFactor", - ) - } - if (is_19_36_or_greater) { miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - DROP_SHADOW_FEATURE_KEY, - "setDropShadow", - ) - - miniplayerModernConstructorMatch.insertLiteralValueBooleanOverride( - ROUNDED_CORNERS_FEATURE_KEY, + MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY, "setRoundedCorners", ) } @@ -551,9 +551,9 @@ val miniplayerPatch = bytecodePatch( invoke-super { p0, p1, p2, p3 }, Landroid/view/ViewGroup;->addView(Landroid/view/View;ILandroid/view/ViewGroup${'$'}LayoutParams;)V invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->playerOverlayGroupCreated(Landroid/view/View;)V return-void - """, + """ ) - }, + } ) // endregion diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/Fingerprints.kt index 9f2e026428..67a6b017d5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/Fingerprints.kt @@ -34,7 +34,7 @@ internal val shortsSeekbarColorFingerprint = fingerprint { literal { reelTimeBarPlayedColorId } } -const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L +internal const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L internal val playerSeekbarGradientConfigFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index c11bd6a0b4..9d68a03a23 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -57,22 +57,62 @@ val enableDebuggingPatch = bytecodePatch( ), ) - // Hook the method that looks up if a feature flag is active or not. - experimentalFeatureFlagFingerprint.applyMatch( + // Hook the methods that look up if a feature flag is active. + + experimentalBooleanFeatureFlagFingerprint.applyMatch( context, experimentalFeatureFlagParentMatch ).mutableMethod.apply { val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) + // It appears that all usage of this method has a default of 'false', + // so there's no need to pass in the default. addInstructions( insertIndex, """ move-result v0 - invoke-static { p1, p2, v0 }, $EXTENSION_CLASS_DESCRIPTOR->isFeatureFlagEnabled(JZ)Z + invoke-static { v0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZJ)Z move-result v0 return v0 """ ) } + + experimentalDoubleFeatureFlagFingerprint.applyMatch( + context, + experimentalFeatureFlagParentMatch + ).mutableMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) + + addInstructions( + insertIndex, + """ + move-result-wide v0 # Also clobbers v1 (p0) since result is wide. + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D + move-result-wide v0 + return-wide v0 + """ + ) + } + + experimentalLongFeatureFlagFingerprint.applyMatch( + context, + experimentalFeatureFlagParentMatch + ).mutableMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) + + addInstructions( + insertIndex, + """ + move-result-wide v0 + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J + move-result-wide v0 + return-wide v0 + """ + ) + } + + // There exists other experimental accessor methods for String, byte[], and wrappers for obfuscated classes, + // but currently none of those are hooked. } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt index 51cc6bac91..549994eb5e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt @@ -10,8 +10,21 @@ internal val experimentalFeatureFlagParentFingerprint = fingerprint { strings("Unable to parse proto typed experiment flag: ") } -internal val experimentalFeatureFlagFingerprint = fingerprint { +internal val experimentalBooleanFeatureFlagFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("Z") parameters("J", "Z") } + +internal val experimentalDoubleFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("D") + parameters("J", "D") +} + +internal val experimentalLongFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("J") + parameters("J", "J") +} + diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/Fingerprints.kt index 35ac5f7ae1..5272adc07d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/cairo/Fingerprints.kt @@ -10,7 +10,7 @@ import com.android.tools.smali.dexlib2.AccessFlags * When this value is true, Cairo Fragment is used. * In this case, some of the patches may be broken, so set this value to FALSE. */ -const val CAIRO_CONFIG_LITERAL_VALUE = 45532100L +internal const val CAIRO_CONFIG_LITERAL_VALUE = 45532100L internal val cairoFragmentConfigFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index f93620055b..d6950433bc 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1035,6 +1035,9 @@ This is because Crowdin requires temporarily flattening this file and removing t Enable drag and drop Drag and drop is enabled\n\nMiniplayer can be dragged to any corner of the screen Drag and drop is disabled + Enable horizontal drag gesture + Horizontal drag gesture enabled\n\nMiniplayer can be dragged off screen to the left or right + Horizontal drag gesture disabled Hide close button Close button is hidden Close button is shown