mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-13 05:37:40 +02:00
feat(YouTube): Add Open channel of live avatar
patch
This commit is contained in:
@ -44,6 +44,14 @@ object PlayerRoutes {
|
||||
"&alt=proto"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_DETAILS: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=videoDetails.channelId"
|
||||
).compile()
|
||||
|
||||
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,61 @@
|
||||
package app.revanced.extension.youtube.patches.general;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.youtube.patches.general.requests.VideoDetailsRequest;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class OpenChannelOfLiveAvatarPatch {
|
||||
private static final boolean OPEN_CHANNEL_OF_LIVE_AVATAR =
|
||||
Settings.OPEN_CHANNEL_OF_LIVE_AVATAR.get();
|
||||
|
||||
private static volatile String videoId = "";
|
||||
private static volatile boolean liveChannelAvatarClicked = false;
|
||||
|
||||
public static void liveChannelAvatarClicked() {
|
||||
liveChannelAvatarClicked = true;
|
||||
}
|
||||
|
||||
public static boolean openChannelOfLiveAvatar() {
|
||||
try {
|
||||
if (!OPEN_CHANNEL_OF_LIVE_AVATAR) {
|
||||
return false;
|
||||
}
|
||||
if (!liveChannelAvatarClicked) {
|
||||
return false;
|
||||
}
|
||||
VideoDetailsRequest request = VideoDetailsRequest.getRequestForVideoId(videoId);
|
||||
if (request != null) {
|
||||
String channelId = request.getInfo();
|
||||
if (channelId != null) {
|
||||
liveChannelAvatarClicked = false;
|
||||
VideoUtils.openChannel(channelId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void openChannelOfLiveAvatar(String newlyLoadedVideoId) {
|
||||
try {
|
||||
if (!OPEN_CHANNEL_OF_LIVE_AVATAR) {
|
||||
return;
|
||||
}
|
||||
if (newlyLoadedVideoId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!liveChannelAvatarClicked) {
|
||||
return;
|
||||
}
|
||||
videoId = newlyLoadedVideoId;
|
||||
VideoDetailsRequest.fetchRequestIfNeeded(newlyLoadedVideoId);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package app.revanced.extension.youtube.patches.general.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class VideoDetailsRequest private constructor(
|
||||
private val videoId: String
|
||||
) {
|
||||
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId)
|
||||
}
|
||||
|
||||
val info: String?
|
||||
get() {
|
||||
try {
|
||||
return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS]
|
||||
} catch (ex: TimeoutException) {
|
||||
Logger.printInfo(
|
||||
{ "getInfo timed out" },
|
||||
ex
|
||||
)
|
||||
} catch (ex: InterruptedException) {
|
||||
Logger.printException(
|
||||
{ "getInfo interrupted" },
|
||||
ex
|
||||
)
|
||||
Thread.currentThread().interrupt() // Restore interrupt status flag.
|
||||
} catch (ex: ExecutionException) {
|
||||
Logger.printException(
|
||||
{ "getInfo failure" },
|
||||
ex
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000L // 20 seconds
|
||||
|
||||
@GuardedBy("itself")
|
||||
val cache: MutableMap<String, VideoDetailsRequest> = Collections.synchronizedMap(
|
||||
object : LinkedHashMap<String, VideoDetailsRequest>(100) {
|
||||
private val CACHE_LIMIT = 50
|
||||
|
||||
override fun removeEldestEntry(eldest: Map.Entry<String, VideoDetailsRequest>): Boolean {
|
||||
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
|
||||
}
|
||||
})
|
||||
|
||||
@JvmStatic
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun fetchRequestIfNeeded(videoId: String) {
|
||||
cache[videoId] = VideoDetailsRequest(videoId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRequestForVideoId(videoId: String): VideoDetailsRequest? {
|
||||
synchronized(cache) {
|
||||
return cache[videoId]
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnectionError(toastMessage: String, ex: Exception?) {
|
||||
Logger.printInfo({ toastMessage }, ex)
|
||||
}
|
||||
|
||||
private fun sendRequest(videoId: String): JSONObject? {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = WebClient.ClientType.MWEB
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_VIDEO_DETAILS,
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == 200) return Requester.parseJSONObject(connection)
|
||||
|
||||
handleConnectionError(
|
||||
(clientTypeName + " not available with response code: "
|
||||
+ responseCode + " message: " + connection.responseMessage),
|
||||
null
|
||||
)
|
||||
} catch (ex: SocketTimeoutException) {
|
||||
handleConnectionError("Connection timeout", ex)
|
||||
} catch (ex: IOException) {
|
||||
handleConnectionError("Network error", ex)
|
||||
} catch (ex: Exception) {
|
||||
Logger.printException({ "sendRequest failed" }, ex)
|
||||
} finally {
|
||||
Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseResponse(videoDetailsJson: JSONObject): String? {
|
||||
try {
|
||||
return videoDetailsJson
|
||||
.getJSONObject("videoDetails")
|
||||
.getString("channelId")
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException ({ "Fetch failed while processing response data for response: $videoDetailsJson" }, e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fetch(videoId: String): String? {
|
||||
val videoDetailsJson = sendRequest(videoId)
|
||||
if (videoDetailsJson != null) {
|
||||
return parseResponse(videoDetailsJson)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -156,6 +156,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE);
|
||||
|
||||
public static final EnumSetting<FormFactor> CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true);
|
||||
public static final BooleanSetting OPEN_CHANNEL_OF_LIVE_AVATAR = new BooleanSetting("revanced_open_channel_of_live_avatar", FALSE, true);
|
||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message");
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION));
|
||||
|
||||
|
@ -29,12 +29,17 @@ import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class VideoUtils extends IntentUtils {
|
||||
private static final String CHANNEL_URL = "https://www.youtube.com/channel/";
|
||||
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
||||
private static final String VIDEO_URL = "https://youtu.be/";
|
||||
private static final String VIDEO_SCHEME_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
|
||||
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%d";
|
||||
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
||||
|
||||
private static String getChannelUrl(String channelId) {
|
||||
return CHANNEL_URL + channelId;
|
||||
}
|
||||
|
||||
private static String getPlaylistUrl(String playlistId) {
|
||||
return PLAYLIST_URL + playlistId;
|
||||
}
|
||||
@ -119,6 +124,10 @@ public class VideoUtils extends IntentUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChannel(@NonNull String channelId) {
|
||||
launchView(getChannelUrl(channelId), getContext().getPackageName());
|
||||
}
|
||||
|
||||
public static void openVideo() {
|
||||
openVideo(VideoInformation.getVideoId());
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package app.revanced.patches.youtube.general.channel
|
||||
|
||||
import app.revanced.patches.youtube.utils.resourceid.elementsImage
|
||||
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 elementsImageFingerprint = legacyFingerprint(
|
||||
name = "elementsImageFingerprint",
|
||||
returnType = "Landroid/view/View;",
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
parameters = listOf("Landroid/view/View;"),
|
||||
literals = listOf(elementsImage),
|
||||
)
|
||||
|
||||
internal val clientSettingEndpointFingerprint = legacyFingerprint(
|
||||
name = "clientSettingEndpointFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L", "Ljava/util/Map;"),
|
||||
strings = listOf(
|
||||
"force_fullscreen",
|
||||
"PLAYBACK_START_DESCRIPTOR_MUTATOR",
|
||||
"VideoPresenterConstants.VIDEO_THUMBNAIL_BITMAP_KEY"
|
||||
),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfPlaybackStartDescriptorInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfPlaybackStartDescriptorInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" &&
|
||||
reference.parameterTypes.isEmpty()
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package app.revanced.patches.youtube.general.channel
|
||||
|
||||
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.patch.bytecodePatch
|
||||
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.OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch
|
||||
import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
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
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$GENERAL_PATH/OpenChannelOfLiveAvatarPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val layoutSwitchPatch = bytecodePatch(
|
||||
OPEN_CHANNEL_OF_LIVE_AVATAR.title,
|
||||
OPEN_CHANNEL_OF_LIVE_AVATAR.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
playbackStartDescriptorPatch,
|
||||
sharedResourceIdPatch,
|
||||
settingsPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
elementsImageFingerprint.methodOrThrow().addInstruction(
|
||||
0,
|
||||
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V"
|
||||
)
|
||||
|
||||
clientSettingEndpointFingerprint.methodOrThrow().apply {
|
||||
val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ)
|
||||
var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE)
|
||||
var freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||
|
||||
addInstructionsWithLabels(
|
||||
eqzIndex, """
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar()Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :ignore
|
||||
return-void
|
||||
:ignore
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1
|
||||
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
|
||||
|
||||
freeIndex = indexOfFirstInstructionOrThrow(playbackStartIndex, Opcode.CONST_STRING)
|
||||
freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
playbackStartIndex + 1, """
|
||||
invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference
|
||||
move-result-object v$freeRegister
|
||||
invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar(Ljava/lang/String;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: GENERAL",
|
||||
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
|
||||
"SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR"
|
||||
),
|
||||
OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
}
|
@ -169,6 +169,10 @@ internal enum class PatchList(
|
||||
"Navigation bar components",
|
||||
"Adds options to hide or change components related to the navigation bar."
|
||||
),
|
||||
OPEN_CHANNEL_OF_LIVE_AVATAR(
|
||||
"Open channel of live avatar",
|
||||
"Adds an option to open channel instead of video when clicking on live avatar."
|
||||
),
|
||||
OPEN_LINKS_EXTERNALLY(
|
||||
"Open links externally",
|
||||
"Adds an option to always open links in your browser instead of in the in-app-browser."
|
||||
|
@ -83,6 +83,8 @@ var easySeekEduContainer = -1L
|
||||
private set
|
||||
var editSettingsAction = -1L
|
||||
private set
|
||||
var elementsImage = -1L
|
||||
private set
|
||||
var endScreenElementLayoutCircle = -1L
|
||||
private set
|
||||
var endScreenElementLayoutIcon = -1L
|
||||
@ -383,6 +385,10 @@ internal val sharedResourceIdPatch = resourcePatch(
|
||||
STRING,
|
||||
"edit_settings_action"
|
||||
]
|
||||
elementsImage = resourceMappings[
|
||||
ID,
|
||||
"elements_image"
|
||||
]
|
||||
endScreenElementLayoutCircle = resourceMappings[
|
||||
LAYOUT,
|
||||
"endscreen_element_layout_circle"
|
||||
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.youtube.video.playbackstart
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
|
||||
const val PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR =
|
||||
"Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;"
|
||||
|
||||
/**
|
||||
* Purpose of this method is not clear, and it's only used to identify
|
||||
* the obfuscated name of the videoId() method in PlaybackStartDescriptor.
|
||||
*/
|
||||
internal val playbackStartFeatureFlagFingerprint = legacyFingerprint(
|
||||
name = "playbackStartFeatureFlagFingerprint",
|
||||
returnType = "Z",
|
||||
parameters = listOf(PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR),
|
||||
literals = listOf(45380134L)
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
package app.revanced.patches.youtube.video.playbackstart
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
|
||||
internal lateinit var playbackStartVideoIdReference: Reference
|
||||
|
||||
val playbackStartDescriptorPatch = bytecodePatch(
|
||||
description = "playbackStartDescriptorPatch"
|
||||
) {
|
||||
dependsOn(sharedResourceIdPatch)
|
||||
|
||||
execute {
|
||||
// Find the obfuscated method name for PlaybackStartDescriptor.videoId()
|
||||
playbackStartFeatureFlagFingerprint.methodOrThrow().apply {
|
||||
val stringMethodIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR
|
||||
&& reference.returnType == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
playbackStartVideoIdReference = getInstruction<ReferenceInstruction>(stringMethodIndex).reference
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,13 +373,6 @@ If the layout of the player screen changes due to server-side changes, unintende
|
||||
<!-- PreferenceScreen: General -->
|
||||
<string name="revanced_preference_screen_general_title">General</string>
|
||||
|
||||
<string name="revanced_change_layout_title">Change layout</string>
|
||||
<string name="revanced_change_layout_entry_1">Original</string>
|
||||
<string name="revanced_change_layout_entry_2">Phone</string>
|
||||
<string name="revanced_change_layout_entry_3">Phone (Max 480 dp)</string>
|
||||
<string name="revanced_change_layout_entry_4">Tablet</string>
|
||||
<string name="revanced_change_layout_entry_5">Tablet (Min 600 dp)</string>
|
||||
|
||||
<string name="revanced_change_start_page_title">Change start page</string>
|
||||
<string name="revanced_change_start_page_entry_default">Default</string>
|
||||
<string name="revanced_change_start_page_entry_browse">Browse channels</string>
|
||||
@ -438,6 +431,15 @@ Limitation: Back button on the toolbar may not work."</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary">"Removes the viewer discretion dialog.
|
||||
This does not bypass the age restriction. It just accepts it automatically."</string>
|
||||
|
||||
<string name="revanced_change_layout_title">Change layout</string>
|
||||
<string name="revanced_change_layout_entry_1">Original</string>
|
||||
<string name="revanced_change_layout_entry_2">Phone</string>
|
||||
<string name="revanced_change_layout_entry_3">Phone (Max 480 dp)</string>
|
||||
<string name="revanced_change_layout_entry_4">Tablet</string>
|
||||
<string name="revanced_change_layout_entry_5">Tablet (Min 600 dp)</string>
|
||||
<string name="revanced_open_channel_of_live_avatar_title">Open channel of live avatar</string>
|
||||
<string name="revanced_open_channel_of_live_avatar_summary_on">Channel opens when the live avatar is clicked.</string>
|
||||
<string name="revanced_open_channel_of_live_avatar_summary_off">Live stream opens when the live avatar is clicked.</string>
|
||||
<string name="revanced_spoof_app_version_title">Spoof app version</string>
|
||||
<string name="revanced_spoof_app_version_summary_on">Version spoofed</string>
|
||||
<string name="revanced_spoof_app_version_summary_off">Version not spoofed</string>
|
||||
|
@ -304,6 +304,9 @@
|
||||
<!-- SETTINGS: LAYOUT_SWITCH
|
||||
<ListPreference android:entries="@array/revanced_change_layout_entries" android:title="@string/revanced_change_layout_title" android:key="revanced_change_layout" android:entryValues="@array/revanced_change_layout_entry_values" />SETTINGS: LAYOUT_SWITCH -->
|
||||
|
||||
<!-- SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||
<SwitchPreference android:title="@string/revanced_open_channel_of_live_avatar_title" android:key="revanced_open_channel_of_live_avatar" android:summaryOn="@string/revanced_open_channel_of_live_avatar_summary_on" android:summaryOff="@string/revanced_open_channel_of_live_avatar_summary_off" />SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR -->
|
||||
|
||||
<!-- SETTINGS: SPOOF_APP_VERSION
|
||||
<SwitchPreference android:title="@string/revanced_spoof_app_version_title" android:key="revanced_spoof_app_version" android:summaryOn="@string/revanced_spoof_app_version_summary_on" android:summaryOff="@string/revanced_spoof_app_version_summary_off" />
|
||||
<app.revanced.extension.shared.settings.preference.WideListPreference android:title="@string/revanced_spoof_app_version_target_entry_title" android:key="revanced_spoof_app_version_target" android:entries="@array/revanced_spoof_app_version_target_entries" android:entryValues="@array/revanced_spoof_app_version_target_entry_values" />
|
||||
@ -885,6 +888,7 @@
|
||||
<Preference android:title="Layout switch" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Miniplayer" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Navigation bar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Open channel of live avatar" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Remove viewer discretion dialog" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Spoof app version" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Toolbar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
|
Reference in New Issue
Block a user