mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-21 10:03:55 +00:00
feat(YouTube - External downloads): Improve the selection of the external downloader package (#5504)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
@@ -1438,6 +1438,28 @@ public class Utils {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a percentage of the screen height to actual device pixels.
|
||||||
|
*
|
||||||
|
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
|
||||||
|
* @return The device pixel value corresponding to the percentage of screen height.
|
||||||
|
*/
|
||||||
|
public static int percentageHeightToPixels(int percentage) {
|
||||||
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
|
return (int) (metrics.heightPixels * (percentage / 100.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a percentage of the screen width to actual device pixels.
|
||||||
|
*
|
||||||
|
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
|
||||||
|
* @return The device pixel value corresponding to the percentage of screen width.
|
||||||
|
*/
|
||||||
|
public static int percentageWidthToPixels(int percentage) {
|
||||||
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
|
return (int) (metrics.widthPixels * (percentage / 100.0f));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
|
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -26,7 +24,7 @@ public class CustomDialogListPreference extends ListPreference {
|
|||||||
/**
|
/**
|
||||||
* Custom ArrayAdapter to handle checkmark visibility.
|
* Custom ArrayAdapter to handle checkmark visibility.
|
||||||
*/
|
*/
|
||||||
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
public static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||||
private static class SubViewDataContainer {
|
private static class SubViewDataContainer {
|
||||||
ImageView checkIcon;
|
ImageView checkIcon;
|
||||||
View placeholder;
|
View placeholder;
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.graphics.drawable.shapes.RectShape;
|
|
||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference.showDialogIfAppIsNotInstalled;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.StringRef;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ public final class DownloadsPatch {
|
|||||||
*
|
*
|
||||||
* Appears to always be called from the main thread.
|
* Appears to always be called from the main thread.
|
||||||
*/
|
*/
|
||||||
public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) {
|
public static boolean inAppDownloadButtonOnClick(String videoId) {
|
||||||
try {
|
try {
|
||||||
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
|
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -48,6 +46,9 @@ public final class DownloadsPatch {
|
|||||||
boolean isActivityContext = true;
|
boolean isActivityContext = true;
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
// Utils context is the application context, and not an activity context.
|
// Utils context is the application context, and not an activity context.
|
||||||
|
//
|
||||||
|
// Edit: This check may no longer be needed since YT can now
|
||||||
|
// only be launched from the main Activity (embedded usage in other apps no longer works).
|
||||||
context = Utils.getContext();
|
context = Utils.getContext();
|
||||||
isActivityContext = false;
|
isActivityContext = false;
|
||||||
}
|
}
|
||||||
@@ -64,8 +65,7 @@ public final class DownloadsPatch {
|
|||||||
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
|
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
|
||||||
* the downloader is opened as a new task (which forces YT to minimize).
|
* the downloader is opened as a new task (which forces YT to minimize).
|
||||||
*/
|
*/
|
||||||
public static void launchExternalDownloader(@NonNull String videoId,
|
public static void launchExternalDownloader(String videoId, Context context, boolean isActivityContext) {
|
||||||
@NonNull Context context, boolean isActivityContext) {
|
|
||||||
try {
|
try {
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
Logger.printDebug(() -> "Launching external downloader with context: " + context);
|
Logger.printDebug(() -> "Launching external downloader with context: " + context);
|
||||||
@@ -73,16 +73,8 @@ public final class DownloadsPatch {
|
|||||||
// Trim string to avoid any accidental whitespace.
|
// Trim string to avoid any accidental whitespace.
|
||||||
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
||||||
|
|
||||||
boolean packageEnabled = false;
|
// If the package is not installed, show a dialog.
|
||||||
try {
|
if (showDialogIfAppIsNotInstalled(context, downloaderPackageName)) {
|
||||||
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
|
|
||||||
} catch (PackageManager.NameNotFoundException error) {
|
|
||||||
Logger.printDebug(() -> "External downloader could not be found: " + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the package is not installed, show the toast
|
|
||||||
if (!packageEnabled) {
|
|
||||||
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
"com.deniscerri.ytdl" /* YTDLnis */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
|
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
|
||||||
|
|
||||||
/**
|
{
|
||||||
* Initialize a settings preference list with the available playback speeds.
|
// Initialize a settings preference list with the available playback speeds.
|
||||||
*/
|
|
||||||
private void initializeEntryValues() {
|
|
||||||
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
|
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
|
||||||
final int numberOfEntries = customPlaybackSpeeds.length + 1;
|
final int numberOfEntries = customPlaybackSpeeds.length + 1;
|
||||||
String[] preferenceListEntries = new String[numberOfEntries];
|
String[] preferenceListEntries = new String[numberOfEntries];
|
||||||
@@ -41,10 +39,6 @@ public final class CustomVideoSpeedListPreference extends CustomDialogListPrefer
|
|||||||
setEntryValues(preferenceListEntryValues);
|
setEntryValues(preferenceListEntryValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
initializeEntryValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,444 @@
|
|||||||
|
package app.revanced.extension.youtube.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.StringRef.sf;
|
||||||
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom ListPreference for selecting an external downloader package with checkmarks and EditText for custom package names.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
|
public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing supported external downloaders with their display names, package names, and download URLs.
|
||||||
|
*/
|
||||||
|
private enum Downloader {
|
||||||
|
YTDLNIS("YTDLnis",
|
||||||
|
"com.deniscerri.ytdl",
|
||||||
|
"https://ytdlnis.org",
|
||||||
|
true),
|
||||||
|
SEAL("Seal",
|
||||||
|
"com.junkfood.seal",
|
||||||
|
"https://github.com/JunkFood02/Seal/releases/latest",
|
||||||
|
true),
|
||||||
|
GRAYJAY("Grayjay",
|
||||||
|
"com.futo.platformplayer",
|
||||||
|
"https://grayjay.app"),
|
||||||
|
LIBRETUBE("LibreTube",
|
||||||
|
"com.github.libretube",
|
||||||
|
"https://libretube.dev"),
|
||||||
|
NEWPIPE("NewPipe",
|
||||||
|
"org.schabi.newpipe",
|
||||||
|
"https://newpipe.net"),
|
||||||
|
PIPEPIPE("PipePipe",
|
||||||
|
"InfinityLoop1309.NewPipeEnhanced",
|
||||||
|
"https://pipepipe.dev"),
|
||||||
|
TUBULAR("Tubular",
|
||||||
|
"org.polymorphicshade.tubular",
|
||||||
|
"https://github.com/polymorphicshade/Tubular/releases/latest"),
|
||||||
|
OTHER(sf("revanced_external_downloader_other_item").toString(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
private static final Map<String, Downloader> PACKAGE_TO_ENUM = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (Downloader downloader : values()) {
|
||||||
|
String packageName = downloader.packageName;
|
||||||
|
if (packageName != null) {
|
||||||
|
PACKAGE_TO_ENUM.put(packageName, downloader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a Downloader by its package name. This method can never return {@link #OTHER}.
|
||||||
|
* @return The Downloader enum or null if not found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Downloader findByPackageName(String packageName) {
|
||||||
|
return PACKAGE_TO_ENUM.get(Objects.requireNonNull(packageName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
@Nullable
|
||||||
|
public final String packageName;
|
||||||
|
@Nullable
|
||||||
|
public final String downloadUrl;
|
||||||
|
/**
|
||||||
|
* If a downloader app should be shown in the preference settings
|
||||||
|
* if the app is not currently installed.
|
||||||
|
*/
|
||||||
|
public final boolean isPreferred;
|
||||||
|
|
||||||
|
Downloader(String name, String packageName, String downloadUrl) {
|
||||||
|
this(name, packageName, downloadUrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Downloader(String name, @Nullable String packageName, @Nullable String downloadUrl, boolean isPreferred) {
|
||||||
|
this.name = name;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.downloadUrl = downloadUrl;
|
||||||
|
this.isPreferred = isPreferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInstalled() {
|
||||||
|
return packageName != null && isAppInstalledAndEnabled(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAppInstalledAndEnabled(String packageName) {
|
||||||
|
try {
|
||||||
|
if (Utils.getContext().getPackageManager().getApplicationInfo(packageName, 0).enabled) {
|
||||||
|
Logger.printDebug(() -> "App installed: " + packageName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException error) {
|
||||||
|
Logger.printDebug(() -> "App not installed: " + packageName);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EditText editText;
|
||||||
|
private CustomDialogListPreference.ListPreferenceArrayAdapter adapter;
|
||||||
|
|
||||||
|
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExternalDownloaderPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExternalDownloaderPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEntries() {
|
||||||
|
List<CharSequence> entries = new ArrayList<>();
|
||||||
|
List<CharSequence> entryValues = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Downloader downloader : Downloader.values()) {
|
||||||
|
if (downloader.isPreferred || downloader.isInstalled()) {
|
||||||
|
String packageName = downloader.packageName;
|
||||||
|
|
||||||
|
entries.add(downloader.name);
|
||||||
|
entryValues.add(packageName != null
|
||||||
|
? packageName
|
||||||
|
: Downloader.OTHER.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setEntries(entries.toArray(new CharSequence[0]));
|
||||||
|
setEntryValues(entryValues.toArray(new CharSequence[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the summary for this ListPreference.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setSummary(CharSequence summary) {
|
||||||
|
// Ignore calls to set the summary.
|
||||||
|
// Summary is always the description of the category.
|
||||||
|
//
|
||||||
|
// This is required otherwise the ReVanced preference fragment
|
||||||
|
// sets all ListPreference summaries to show the current selection.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a custom dialog with a ListView for predefined downloader packages and EditText for custom package input.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void showDialog(@Nullable Bundle state) {
|
||||||
|
// Must set entries before showing the dialog, to handle if
|
||||||
|
// an app is installed while the settings are open in the background.
|
||||||
|
updateEntries();
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
String packageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||||
|
|
||||||
|
// Create the main layout for the dialog content.
|
||||||
|
LinearLayout contentLayout = new LinearLayout(context);
|
||||||
|
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
// Create ListView for predefined downloader apps.
|
||||||
|
ListView listView = new ListView(context);
|
||||||
|
listView.setId(android.R.id.list);
|
||||||
|
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||||
|
|
||||||
|
// Create custom adapter for the ListView.
|
||||||
|
final boolean usingCustomDownloader = Downloader.findByPackageName(packageName) == null;
|
||||||
|
adapter = new CustomDialogListPreference.ListPreferenceArrayAdapter(
|
||||||
|
context,
|
||||||
|
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
|
||||||
|
getEntries(),
|
||||||
|
getEntryValues(),
|
||||||
|
usingCustomDownloader
|
||||||
|
? Downloader.OTHER.name
|
||||||
|
: packageName
|
||||||
|
);
|
||||||
|
listView.setAdapter(adapter);
|
||||||
|
|
||||||
|
Function<String, Void> updateListViewSelection = (updatedPackageName) -> {
|
||||||
|
String entryValueName = Downloader.findByPackageName(updatedPackageName) == null
|
||||||
|
? Downloader.OTHER.name
|
||||||
|
: updatedPackageName;
|
||||||
|
CharSequence[] entryValues = getEntryValues();
|
||||||
|
|
||||||
|
for (int i = 0, length = entryValues.length; i < length; i++) {
|
||||||
|
String entryString = entryValues[i].toString();
|
||||||
|
if (entryString.equals(entryValueName)) {
|
||||||
|
listView.setItemChecked(i, true);
|
||||||
|
listView.setSelection(i);
|
||||||
|
adapter.setSelectedValue(entryString);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
updateListViewSelection.apply(packageName);
|
||||||
|
|
||||||
|
// Handle item click to select value.
|
||||||
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
String selectedValue = getEntryValues()[position].toString();
|
||||||
|
Downloader selectedApp = Downloader.findByPackageName(selectedValue);
|
||||||
|
|
||||||
|
if (selectedApp != null) {
|
||||||
|
editText.setText(selectedApp.packageName);
|
||||||
|
editText.setEnabled(false); // Disable editing for predefined options.
|
||||||
|
} else {
|
||||||
|
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||||
|
editText.setText(Downloader.findByPackageName(savedPackageName) == null
|
||||||
|
? savedPackageName // If the user is clicking thru options then retain existing other app.
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
editText.setEnabled(true); // Enable editing for Custom.
|
||||||
|
editText.requestFocus();
|
||||||
|
}
|
||||||
|
editText.setSelection(editText.getText().length());
|
||||||
|
adapter.setSelectedValue(selectedValue);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add ListView to content layout with initial height.
|
||||||
|
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0 // Initial height, will be updated.
|
||||||
|
);
|
||||||
|
listViewParams.bottomMargin = dipToPixels(16);
|
||||||
|
contentLayout.addView(listView, listViewParams);
|
||||||
|
|
||||||
|
// Add EditText for custom package name.
|
||||||
|
editText = new EditText(context);
|
||||||
|
editText.setText(packageName);
|
||||||
|
editText.setSelection(packageName.length());
|
||||||
|
editText.setHint(str("revanced_external_downloader_other_item_hint"));
|
||||||
|
editText.setSingleLine(true); // Restrict EditText to a single line.
|
||||||
|
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||||
|
// Set initial EditText state based on selected downloader.
|
||||||
|
editText.setEnabled(usingCustomDownloader);
|
||||||
|
editText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable edit) {
|
||||||
|
String updatedPackageName = edit.toString().trim();
|
||||||
|
updateListViewSelection.apply(updatedPackageName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
|
Utils.createCornerRadii(10), null, null));
|
||||||
|
editTextBackground.getPaint().setColor(Utils.getEditTextBackground());
|
||||||
|
final int dip8 = dipToPixels(8);
|
||||||
|
editText.setPadding(dip8, dip8, dip8, dip8);
|
||||||
|
editText.setBackground(editTextBackground);
|
||||||
|
editText.setClipToOutline(true);
|
||||||
|
contentLayout.addView(editText);
|
||||||
|
|
||||||
|
// Create the custom dialog.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
getTitle() != null ? getTitle().toString() : "",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
() -> {
|
||||||
|
String newValue = editText.getText().toString().trim();
|
||||||
|
if (newValue.isEmpty()) {
|
||||||
|
// Show dialog if EditText is empty.
|
||||||
|
Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
str("revanced_external_downloader_name_title"),
|
||||||
|
str("revanced_external_downloader_empty_warning"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
() -> {}, // OK button does nothing (dismiss only).
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
).first.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialogIfAppIsNotInstalled(getContext(), newValue)) {
|
||||||
|
return; // Invalid package. Do not save.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save custom package name.
|
||||||
|
if (callChangeListener(newValue)) {
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_settings_reset"),
|
||||||
|
() -> { // Reset action.
|
||||||
|
String defaultValue = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.defaultValue;
|
||||||
|
editText.setText(defaultValue);
|
||||||
|
editText.setSelection(defaultValue.length());
|
||||||
|
editText.setEnabled(false); // Disable editing on reset.
|
||||||
|
updateListViewSelection.apply(defaultValue);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the content layout directly to the dialog's main layout.
|
||||||
|
LinearLayout dialogMainLayout = dialogPair.second;
|
||||||
|
dialogMainLayout.addView(contentLayout, dialogMainLayout.getChildCount() - 1);
|
||||||
|
|
||||||
|
// Update ListView height dynamically based on orientation.
|
||||||
|
//noinspection ExtractMethodRecommender
|
||||||
|
Runnable updateListViewHeight = () -> {
|
||||||
|
int totalHeight = 0;
|
||||||
|
ListAdapter listAdapter = listView.getAdapter();
|
||||||
|
if (listAdapter != null) {
|
||||||
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
|
final int listAdapterCount = listAdapter.getCount();
|
||||||
|
for (int i = 0; i < listAdapterCount; i++) {
|
||||||
|
View item = listAdapter.getView(i, null, listView);
|
||||||
|
item.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.AT_MOST),
|
||||||
|
View.MeasureSpec.UNSPECIFIED
|
||||||
|
);
|
||||||
|
totalHeight += item.getMeasuredHeight();
|
||||||
|
}
|
||||||
|
totalHeight += listView.getDividerHeight() * (listAdapterCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int orientation = context.getResources().getConfiguration().orientation;
|
||||||
|
if (orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
// In portrait orientation, use WRAP_CONTENT for ListView height.
|
||||||
|
listViewParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
|
||||||
|
} else {
|
||||||
|
// In landscape orientation, limit ListView height to 30% of screen height.
|
||||||
|
final int maxHeight = Utils.percentageHeightToPixels(30);
|
||||||
|
listViewParams.height = Math.min(totalHeight, maxHeight);
|
||||||
|
}
|
||||||
|
listView.setLayoutParams(listViewParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial height calculation.
|
||||||
|
updateListViewHeight.run();
|
||||||
|
|
||||||
|
// Listen for configuration changes (e.g., orientation).
|
||||||
|
View dialogView = dialogPair.second;
|
||||||
|
// Recalculate height when layout changes (e.g., orientation change).
|
||||||
|
dialogView.getViewTreeObserver().addOnGlobalLayoutListener(updateListViewHeight::run);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the app is not installed and a dialog was shown.
|
||||||
|
*/
|
||||||
|
public static boolean showDialogIfAppIsNotInstalled(Context context, String packageName) {
|
||||||
|
if (isAppInstalledAndEnabled(packageName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Downloader downloader = Downloader.findByPackageName(packageName);
|
||||||
|
String downloadUrl = downloader != null
|
||||||
|
? downloader.downloadUrl
|
||||||
|
: null;
|
||||||
|
String okButtonText = downloadUrl != null
|
||||||
|
? str("gms_core_dialog_open_website_text") // Open website.
|
||||||
|
: null; // Ok.
|
||||||
|
// Show a dialog if the recommended app is not installed or if the custom package cannot be found.
|
||||||
|
String message = downloader != null
|
||||||
|
? str("revanced_external_downloader_not_installed_warning", downloader.name)
|
||||||
|
: str("revanced_external_downloader_package_not_found_warning", packageName);
|
||||||
|
|
||||||
|
Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
str("revanced_external_downloader_not_found_title"),
|
||||||
|
message,
|
||||||
|
null,
|
||||||
|
okButtonText,
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
// OK button action: open the downloader's URL if available.
|
||||||
|
if (downloadUrl != null) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl));
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to open downloader URL: " + downloader, ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
).first.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -830,11 +830,10 @@ public class SegmentPlaybackController {
|
|||||||
WindowManager.LayoutParams params = window.getAttributes();
|
WindowManager.LayoutParams params = window.getAttributes();
|
||||||
params.gravity = Gravity.BOTTOM;
|
params.gravity = Gravity.BOTTOM;
|
||||||
params.y = dipToPixels(72);
|
params.y = dipToPixels(72);
|
||||||
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
int portraitWidth = Utils.percentageWidthToPixels(60); // 60% of the screen width.
|
||||||
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
|
|
||||||
|
|
||||||
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
|
portraitWidth = Math.min(portraitWidth, Utils.percentageHeightToPixels(60)); // 60% of the screen height.
|
||||||
}
|
}
|
||||||
params.width = portraitWidth;
|
params.width = portraitWidth;
|
||||||
params.dimAmount = 0.0f;
|
params.dimAmount = 0.0f;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import app.revanced.patcher.patch.bytecodePatch
|
|||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
@@ -40,7 +39,10 @@ private val downloadsResourcePatch = resourcePatch {
|
|||||||
preferences = setOf(
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_external_downloader"),
|
SwitchPreference("revanced_external_downloader"),
|
||||||
SwitchPreference("revanced_external_downloader_action_button"),
|
SwitchPreference("revanced_external_downloader_action_button"),
|
||||||
TextPreference("revanced_external_downloader_name", inputType = InputType.TEXT),
|
TextPreference(
|
||||||
|
"revanced_external_downloader_name",
|
||||||
|
tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -531,8 +531,15 @@ This feature is only available for older devices"</string>
|
|||||||
<string name="revanced_external_downloader_action_button_summary_on">Download button opens your external downloader</string>
|
<string name="revanced_external_downloader_action_button_summary_on">Download button opens your external downloader</string>
|
||||||
<string name="revanced_external_downloader_action_button_summary_off">Download button opens the native in-app downloader</string>
|
<string name="revanced_external_downloader_action_button_summary_off">Download button opens the native in-app downloader</string>
|
||||||
<string name="revanced_external_downloader_name_title">Downloader package name</string>
|
<string name="revanced_external_downloader_name_title">Downloader package name</string>
|
||||||
<string name="revanced_external_downloader_name_summary">Package name of your installed external downloader app, such as NewPipe or Seal</string>
|
<string name="revanced_external_downloader_name_summary">Package name of your installed external downloader app</string>
|
||||||
|
<string name="revanced_external_downloader_other_item_hint">Enter the package name</string>
|
||||||
|
<string name="revanced_external_downloader_other_item">Other</string>
|
||||||
|
<string name="revanced_external_downloader_not_found_title">App not installed</string>
|
||||||
<string name="revanced_external_downloader_not_installed_warning">%s is not installed. Please install it.</string>
|
<string name="revanced_external_downloader_not_installed_warning">%s is not installed. Please install it.</string>
|
||||||
|
<string name="revanced_external_downloader_package_not_found_warning">"Could not find installed app with package name: %s
|
||||||
|
|
||||||
|
Verify the package name is correct and the app is installed"</string>
|
||||||
|
<string name="revanced_external_downloader_empty_warning">The package name cannot be empty</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||||
<string name="revanced_disable_precise_seeking_gesture_title">Disable precise seeking gesture</string>
|
<string name="revanced_disable_precise_seeking_gesture_title">Disable precise seeking gesture</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user