diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index 168ef7375..fead7fcc8 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -22,16 +22,16 @@ import android.widget.ListView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Objects; - import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.shared.utils.StringRef; import app.revanced.extension.shared.utils.Utils; @SuppressWarnings({"unused", "deprecation"}) public abstract class AbstractPreferenceFragment extends PreferenceFragment { + /** * Indicates that if a preference changes, * to apply the change from the Setting to the UI component. @@ -39,7 +39,11 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { public static boolean settingImportInProgress; /** - * Confirm and restart dialog button text and title. + * Prevents recursive calls during preference <-> UI syncing from showing extra dialogs. + */ + private static boolean updatingPreference; + + /** * Set by subclasses if Strings cannot be added as a resource. */ @Nullable @@ -52,7 +56,14 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { - Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); + if (updatingPreference) { + Logger.printDebug(() -> "Ignoring preference change as sync is in progress"); + return; + } + if (str == null) { + return; + } + Setting setting = Setting.getSettingFromPath(str); if (setting == null) { return; } @@ -73,10 +84,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } } + updatingPreference = true; // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. + // Updating here can can cause a recursive call back into this same method. updatePreference(pref, setting, true, settingImportInProgress); // Update any other preference availability that may now be different. updateUIAvailability(); + updatingPreference = false; } catch (Exception ex) { Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex); } @@ -103,29 +117,32 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { Utils.verifyOnMainThread(); final var context = getActivity(); - showingUserDialogMessage = true; - assert setting.userDialogMessage != null; - new AlertDialog.Builder(context) - .setTitle(android.R.string.dialog_alert_title) - .setMessage(setting.userDialogMessage.toString()) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - // User confirmed, save to the Setting. - updatePreference(pref, setting, true, false); + final StringRef userDialogMessage = setting.userDialogMessage; + if (context != null && userDialogMessage != null) { + showingUserDialogMessage = true; - // Update availability of other preferences that may be changed. - updateUIAvailability(); + new AlertDialog.Builder(context) + .setTitle(android.R.string.dialog_alert_title) + .setMessage(userDialogMessage.toString()) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + // User confirmed, save to the Setting. + updatePreference(pref, setting, true, false); - if (setting.rebootApp) { - showRestartDialog(context); - } - }) - .setNegativeButton(android.R.string.cancel, (dialog, id) -> { - // Restore whatever the setting was before the change. - updatePreference(pref, setting, true, true); - }) - .setOnDismissListener(dialog -> showingUserDialogMessage = false) - .setCancelable(false) - .show(); + // Update availability of other preferences that may be changed. + updateUIAvailability(); + + if (setting.rebootApp) { + showRestartDialog(context); + } + }) + .setNegativeButton(android.R.string.cancel, (dialog, id) -> { + // Restore whatever the setting was before the change. + updatePreference(pref, setting, true, true); + }) + .setOnDismissListener(dialog -> showingUserDialogMessage = false) + .setCancelable(false) + .show(); + } } /** @@ -146,14 +163,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { * @return If the preference is currently set to the default value of the Setting. */ protected boolean prefIsSetToDefault(Preference pref, Setting setting) { + Object defaultValue = setting.defaultValue; if (pref instanceof SwitchPreference switchPref) { - return switchPref.isChecked() == (Boolean) setting.defaultValue; + return switchPref.isChecked() == (Boolean) defaultValue; } + String defaultValueString = defaultValue.toString(); if (pref instanceof EditTextPreference editPreference) { - return editPreference.getText().equals(setting.defaultValue.toString()); + return editPreference.getText().equals(defaultValueString); } if (pref instanceof ListPreference listPref) { - return listPref.getValue().equals(setting.defaultValue.toString()); + return listPref.getValue().equals(defaultValueString); } throw new IllegalStateException("Must override method to handle " @@ -258,18 +277,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { listPreference.setSummary(objectStringValue); } - public static void showRestartDialog(@NonNull final Context context) { + public static void showRestartDialog(@NonNull Context context) { if (restartDialogMessage == null) { restartDialogMessage = str("revanced_extended_restart_message"); } + showRestartDialog(context, restartDialogMessage); } - public static void showRestartDialog(@NonNull final Context context, String message) { + public static void showRestartDialog(@NonNull Context context, String message) { showRestartDialog(context, message, 0); } - public static void showRestartDialog(@NonNull final Context context, String message, long delay) { + public static void showRestartDialog(@NonNull Context context, String message, long delay) { Utils.verifyOnMainThread(); new AlertDialog.Builder(context) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index 77bf67050..0fa29e929 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -31,6 +31,7 @@ import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; +import android.util.Pair; import android.util.TypedValue; import android.view.ViewGroup; import android.view.WindowInsets; @@ -55,12 +56,15 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; +import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.settings.Setting; 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.youtube.patches.video.CustomPlaybackSpeedPatch; +import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.utils.ExtendedUtils; import app.revanced.extension.youtube.utils.ThemeUtils; @@ -74,14 +78,19 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { @SuppressLint("SuspiciousIndentation") private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { - if (str == null) return; - Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); + if (str == null) { + return; + } - if (setting == null) return; + Setting setting = Setting.getSettingFromPath(str); + if (setting == null) { + return; + } Preference mPreference = findPreference(str); - - if (mPreference == null) return; + if (mPreference == null) { + return; + } if (mPreference instanceof SwitchPreference switchPreference) { BooleanSetting boolSetting = (BooleanSetting) setting; @@ -122,17 +131,13 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { ReVancedSettingsPreference.initializeReVancedSettings(); - if (settingImportInProgress) { - return; - } - - if (!showingUserDialogMessage) { + if (!settingImportInProgress && !showingUserDialogMessage) { final Context context = getActivity(); - if (setting.userDialogMessage != null - && mPreference instanceof SwitchPreference switchPreference - && setting.defaultValue instanceof Boolean defaultValue - && switchPreference.isChecked() != defaultValue) { + if (setting.userDialogMessage != null && + mPreference instanceof SwitchPreference switchPreference && + setting.defaultValue instanceof Boolean defaultValue && + switchPreference.isChecked() != defaultValue) { showSettingUserDialogConfirmation(context, switchPreference, (BooleanSetting) setting); } else if (setting.rebootApp) { showRestartDialog(context); @@ -314,6 +319,10 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity()); copyPreferences(getPreferenceScreen(), originalPreferenceScreen); + + sortPreferenceListMenu(Settings.CHANGE_START_PAGE); + sortPreferenceListMenu(Settings.SPOOF_STREAMING_DATA_LANGUAGE); + sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE); } catch (Exception th) { Logger.printException(() -> "Error during onCreate()", th); } @@ -331,6 +340,65 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { super.onDestroy(); } + /** + * Sorts a preference list by menu entries, but preserves the first value as the first entry. + * + * @noinspection SameParameterValue + */ + private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) { + CharSequence[] entries = listPreference.getEntries(); + CharSequence[] entryValues = listPreference.getEntryValues(); + final int entrySize = entries.length; + + if (entrySize != entryValues.length) { + // Xml array declaration has a missing/extra entry. + throw new IllegalStateException(); + } + + // Since the text of Preference is Spanned, CharSequence#toString() should not be used. + // If CharSequence#toString() is used, Spanned styling, such as HTML syntax, will be broken. + List> firstPairs = new ArrayList<>(firstEntriesToPreserve); + List> pairsToSort = new ArrayList<>(entrySize); + + for (int i = 0; i < entrySize; i++) { + Pair pair = new Pair<>(entries[i], entryValues[i]); + if (i < firstEntriesToPreserve) { + firstPairs.add(pair); + } else { + pairsToSort.add(pair); + } + } + + pairsToSort.sort((pair1, pair2) + -> pair1.first.toString().compareToIgnoreCase(pair2.first.toString())); + + CharSequence[] sortedEntries = new CharSequence[entrySize]; + CharSequence[] sortedEntryValues = new CharSequence[entrySize]; + + int i = 0; + for (Pair pair : firstPairs) { + sortedEntries[i] = pair.first; + sortedEntryValues[i] = pair.second; + i++; + } + + for (Pair pair : pairsToSort) { + sortedEntries[i] = pair.first; + sortedEntryValues[i] = pair.second; + i++; + } + + listPreference.setEntries(sortedEntries); + listPreference.setEntryValues(sortedEntryValues); + } + + private void sortPreferenceListMenu(EnumSetting setting) { + Preference preference = findPreference(setting.key); + if (preference instanceof ListPreference languagePreference) { + sortListPreferenceByValues(languagePreference, 1); + } + } + /** * Recursively stores all preferences and their dependencies grouped by their parent PreferenceGroup. * diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index 7809e27e0..2f3c79735 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -24,58 +24,116 @@ Please download %2$s from the website." %s is not installed. Please install it. RVX language App language - Arabic - Azerbaijani - Bulgarian - Bengali - Catalan - Czech - Danish - German - Greek - English - Spanish - Estonian - Persian - Finnish - French - Gujarati - Hindi - Croatian - Hungarian - Indonesian - Italian - Japanese - Kazakh - Korean - Lithuanian - Latvian - Macedonian - Mongolian - Marathi - Malay - Burmese - Dutch - Odia - Punjabi - Polish - Portuguese - Romanian - Russian - Slovak - Slovene - Serbian - Swedish - Swahili - Tamil - Telugu - Thai - Turkish - Ukrainian - Urdu - Vietnamese - Chinese - + "Amharic +አማርኛ" + "Arabic +العربية" + "Azerbaijani +Azərbaycan" + "Belarusian +беларуская" + "Bulgarian +Български" + "Bengali +বাংলা" + "Catalan +Català" + "Czech +Čeština" + "Danish +Dansk" + "German +Deutsch" + "Greek +Ελληνικά" + "English +English" + "Spanish +Español" + "Estonian +Eesti" + "Persian +فارسی" + "Finnish +Suomi" + "French +Français" + "Gujarati +ગુજરાતી" + "Hebrew +עברי" + "Hindi +हिन्दी" + "Croatian +Hrvatski" + "Hungarian +Magyar" + "Indonesian +Indonesia" + "Italian +Italiano" + "Japanese +日本語" + "Kazakh +Қазақ тілі" + "Korean +한국어" + "Lithuanian +Lietuvių" + "Latvian +Latviešu" + "Macedonian +Македонски" + "Mongolian +Монгол" + "Marathi +मराठी" + "Malay +Melayu" + "Burmese +ဗမာ" + "Dutch +Nederlands" + "Odia +ଓଡ଼ିଆ" + "Punjabi +ਪੰਜਾਬੀ" + "Polish +Polski" + "Portuguese +Português" + "Romanian +Română" + "Russian +Русский" + "Slovak +Slovenčina" + "Albanian +Shqip" + "Slovene +Slovenščina" + "Serbian +Српски" + "Swedish +Svenska" + "Swahili +Kiswahili" + "Tamil +தமிழ்" + "Telugu +తెలుగు" + "Thai +ไทย" + "Turkish +Türkçe" + "Ukrainian +Українська" + "Urdu +اردو" + "Vietnamese +Tiếng Việt" + "Chinese +中文" Ads