mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 21:27:43 +02:00
feat(YouTube): Add Change form factor
, Remove Change layout
patch
This commit is contained in:
@ -24,9 +24,6 @@ import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.StringRef;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
/**
|
||||
* @noinspection rawtypes
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Setting<T> {
|
||||
|
||||
@ -128,6 +125,7 @@ public abstract class Setting<T> {
|
||||
|
||||
/**
|
||||
* @return All settings that have been created, sorted by keys.
|
||||
* @noinspection Java8ListSort
|
||||
*/
|
||||
@NonNull
|
||||
private static List<Setting<?>> allLoadedSettingsSorted() {
|
||||
@ -171,7 +169,6 @@ public abstract class Setting<T> {
|
||||
|
||||
/**
|
||||
* Confirmation message to display, if the user tries to change the setting from the default value.
|
||||
* Currently this works only for Boolean setting types.
|
||||
*/
|
||||
@Nullable
|
||||
public final StringRef userDialogMessage;
|
||||
@ -271,6 +268,7 @@ public abstract class Setting<T> {
|
||||
* <p>
|
||||
* This method will be deleted in the future.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||
return; // Nothing to do.
|
||||
@ -452,6 +450,7 @@ public abstract class Setting<T> {
|
||||
|
||||
boolean rebootSettingChanged = false;
|
||||
int numberOfSettingsImported = 0;
|
||||
//noinspection rawtypes
|
||||
for (Setting setting : SETTINGS) {
|
||||
String key = setting.getImportExportKey();
|
||||
if (json.has(key)) {
|
||||
|
@ -12,6 +12,7 @@ import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
@ -21,6 +22,9 @@ 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;
|
||||
@ -48,10 +52,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||
try {
|
||||
if (str == null) {
|
||||
return;
|
||||
}
|
||||
Setting<?> setting = Setting.getSettingFromPath(str);
|
||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||
if (setting == null) {
|
||||
return;
|
||||
}
|
||||
@ -59,24 +60,23 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
Logger.printDebug(() -> "Preference changed: " + setting.key);
|
||||
|
||||
if (!settingImportInProgress && !showingUserDialogMessage) {
|
||||
if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) {
|
||||
// Do not change the setting yet, to allow preserving whatever
|
||||
// list/text value was previously set if it needs to be reverted.
|
||||
showSettingUserDialogConfirmation(pref, setting);
|
||||
return;
|
||||
} else if (setting.rebootApp) {
|
||||
showRestartDialog(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||
updatePreference(pref, setting, true, settingImportInProgress);
|
||||
// Update any other preference availability that may now be different.
|
||||
updateUIAvailability();
|
||||
|
||||
if (settingImportInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showingUserDialogMessage) {
|
||||
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
||||
showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting);
|
||||
} else if (setting.rebootApp) {
|
||||
showRestartDialog(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||
}
|
||||
@ -90,14 +90,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
* so all app specific {@link Setting} instances are loaded before this method returns.
|
||||
*/
|
||||
protected void initialize() {
|
||||
final int id = getXmlIdentifier("revanced_prefs");
|
||||
final int identifier = getXmlIdentifier("revanced_prefs");
|
||||
if (identifier == 0) return;
|
||||
addPreferencesFromResource(identifier);
|
||||
|
||||
if (id == 0) return;
|
||||
addPreferencesFromResource(id);
|
||||
Utils.sortPreferenceGroups(getPreferenceScreen());
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
Utils.sortPreferenceGroups(screen);
|
||||
Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
|
||||
}
|
||||
|
||||
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
|
||||
private void showSettingUserDialogConfirmation(Preference pref, Setting<?> setting) {
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
final var context = getActivity();
|
||||
@ -107,12 +109,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
.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);
|
||||
|
||||
// Update availability of other preferences that may be changed.
|
||||
updateUIAvailability();
|
||||
|
||||
if (setting.rebootApp) {
|
||||
showRestartDialog(context);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||
switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
|
||||
// Restore whatever the setting was before the change.
|
||||
updatePreference(pref, setting, true, true);
|
||||
})
|
||||
.setOnDismissListener(dialog -> showingUserDialogMessage = false)
|
||||
.setCancelable(false)
|
||||
@ -123,7 +132,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
* Updates all Preferences values and their availability using the current values in {@link Setting}.
|
||||
*/
|
||||
protected void updateUIToSettingValues() {
|
||||
updatePreferenceScreen(getPreferenceScreen(), true, true);
|
||||
updatePreferenceScreen(getPreferenceScreen(), true,true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,24 +142,48 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
updatePreferenceScreen(getPreferenceScreen(), false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the preference is currently set to the default value of the Setting.
|
||||
*/
|
||||
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
||||
if (pref instanceof SwitchPreference switchPref) {
|
||||
return switchPref.isChecked() == (Boolean) setting.defaultValue;
|
||||
}
|
||||
if (pref instanceof EditTextPreference editPreference) {
|
||||
return editPreference.getText().equals(setting.defaultValue.toString());
|
||||
}
|
||||
if (pref instanceof ListPreference listPref) {
|
||||
return listPref.getValue().equals(setting.defaultValue.toString());
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Must override method to handle "
|
||||
+ "preference type: " + pref.getClass());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Syncs all UI Preferences to any {@link Setting} they represent.
|
||||
*/
|
||||
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
|
||||
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||
boolean syncSettingValue,
|
||||
boolean applySettingToPreference) {
|
||||
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||
// the Preferences.
|
||||
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference pref = screen.getPreference(i);
|
||||
if (pref instanceof PreferenceScreen preferenceScreen) {
|
||||
updatePreferenceScreen(preferenceScreen, syncSettingValue, applySettingToPreference);
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference pref = group.getPreference(i);
|
||||
if (pref instanceof PreferenceGroup subGroup) {
|
||||
updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference);
|
||||
} else if (pref.hasKey()) {
|
||||
String key = pref.getKey();
|
||||
Setting<?> setting = Setting.getSettingFromPath(key);
|
||||
|
||||
if (setting != null) {
|
||||
updatePreference(pref, setting, syncSettingValue, applySettingToPreference);
|
||||
} else if (BaseSettings.ENABLE_DEBUG_LOGGING.get() && (pref instanceof SwitchPreference
|
||||
|| pref instanceof EditTextPreference || pref instanceof ListPreference)) {
|
||||
// Probably a typo in the patches preference declaration.
|
||||
Logger.printException(() -> "Preference key has no setting: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,26 +199,26 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
protected void syncSettingWithPreference(@NonNull Preference pref,
|
||||
@NonNull Setting<?> setting,
|
||||
boolean applySettingToPreference) {
|
||||
if (pref instanceof SwitchPreference switchPreference) {
|
||||
if (pref instanceof SwitchPreference switchPref) {
|
||||
BooleanSetting boolSetting = (BooleanSetting) setting;
|
||||
if (applySettingToPreference) {
|
||||
switchPreference.setChecked(boolSetting.get());
|
||||
switchPref.setChecked(boolSetting.get());
|
||||
} else {
|
||||
BooleanSetting.privateSetValue(boolSetting, switchPreference.isChecked());
|
||||
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
||||
}
|
||||
} else if (pref instanceof EditTextPreference editTextPreference) {
|
||||
} else if (pref instanceof EditTextPreference editPreference) {
|
||||
if (applySettingToPreference) {
|
||||
editTextPreference.setText(setting.get().toString());
|
||||
editPreference.setText(setting.get().toString());
|
||||
} else {
|
||||
Setting.privateSetValueFromString(setting, editTextPreference.getText());
|
||||
Setting.privateSetValueFromString(setting, editPreference.getText());
|
||||
}
|
||||
} else if (pref instanceof ListPreference listPreference) {
|
||||
} else if (pref instanceof ListPreference listPref) {
|
||||
if (applySettingToPreference) {
|
||||
listPreference.setValue(setting.get().toString());
|
||||
listPref.setValue(setting.get().toString());
|
||||
} else {
|
||||
Setting.privateSetValueFromString(setting, listPreference.getValue());
|
||||
Setting.privateSetValueFromString(setting, listPref.getValue());
|
||||
}
|
||||
updateListPreferenceSummary(listPreference, setting);
|
||||
updateListPreferenceSummary(listPref, setting);
|
||||
} else {
|
||||
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
|
||||
}
|
||||
@ -194,7 +227,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
/**
|
||||
* Updates a UI Preference with the {@link Setting} that backs it.
|
||||
*
|
||||
* @param syncSetting If the UI should be synced {@link Setting} <-> Preference
|
||||
* @param syncSetting If the UI should be synced {@link Setting} <-> Preference
|
||||
* @param applySettingToPreference If true, then apply {@link Setting} -> Preference.
|
||||
* If false, then apply {@link Setting} <- Preference.
|
||||
*/
|
||||
|
@ -3,6 +3,8 @@ package app.revanced.extension.shared.utils;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
@ -13,6 +15,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.Preference;
|
||||
@ -43,11 +46,11 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import kotlin.text.Regex;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Utils {
|
||||
@ -529,6 +532,81 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ignore this class. It must be public to satisfy Android requirements.
|
||||
*/
|
||||
public static final class DialogFragmentWrapper extends DialogFragment {
|
||||
|
||||
private Dialog dialog;
|
||||
@Nullable
|
||||
private DialogFragmentOnStartAction onStartAction;
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
// Do not call super method to prevent state saving.
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
try {
|
||||
super.onStart();
|
||||
|
||||
if (onStartAction != null) {
|
||||
onStartAction.onStart((AlertDialog) getDialog());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onStart failure: " + dialog.getClass().getSimpleName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for {@link #showDialog(Activity, AlertDialog, boolean, DialogFragmentOnStartAction)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DialogFragmentOnStartAction {
|
||||
void onStart(AlertDialog dialog);
|
||||
}
|
||||
|
||||
public static void showDialog(Activity activity, AlertDialog dialog) {
|
||||
showDialog(activity, dialog, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to allow showing an AlertDialog on top of other alert dialogs.
|
||||
* Calling this will always display the dialog on top of all other dialogs
|
||||
* previously called using this method.
|
||||
* <br>
|
||||
* Be aware the on start action can be called multiple times for some situations,
|
||||
* such as the user switching apps without dismissing the dialog then switching back to this app.
|
||||
*<br>
|
||||
* This method is only useful during app startup and multiple patches may show their own dialog,
|
||||
* and the most important dialog can be called last (using a delay) so it's always on top.
|
||||
*<br>
|
||||
* For all other situations it's better to not use this method and
|
||||
* call {@link AlertDialog#show()} on the dialog.
|
||||
*/
|
||||
public static void showDialog(Activity activity,
|
||||
AlertDialog dialog,
|
||||
boolean isCancelable,
|
||||
@Nullable DialogFragmentOnStartAction onStartAction) {
|
||||
verifyOnMainThread();
|
||||
|
||||
DialogFragmentWrapper fragment = new DialogFragmentWrapper();
|
||||
fragment.dialog = dialog;
|
||||
fragment.onStartAction = onStartAction;
|
||||
fragment.setCancelable(isCancelable);
|
||||
|
||||
fragment.show(activity.getFragmentManager(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe to call from any thread
|
||||
*/
|
||||
@ -737,14 +815,14 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Regex punctuationRegex = new Regex("\\p{P}+");
|
||||
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
|
||||
|
||||
/**
|
||||
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
|
||||
*/
|
||||
public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) {
|
||||
if (original == null) return "";
|
||||
return punctuationRegex.replace(original, "").toLowerCase();
|
||||
return punctuationPattern.matcher(original).replaceAll("").toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -763,8 +841,8 @@ public class Utils {
|
||||
Preference preference = group.getPreference(i);
|
||||
|
||||
final Sort preferenceSort;
|
||||
if (preference instanceof PreferenceGroup preferenceGroup) {
|
||||
sortPreferenceGroups(preferenceGroup);
|
||||
if (preference instanceof PreferenceGroup subGroup) {
|
||||
sortPreferenceGroups(subGroup);
|
||||
preferenceSort = groupSort; // Sort value for groups is for it's content, not itself.
|
||||
} else {
|
||||
// Allow individual preferences to set a key sorting.
|
||||
@ -774,13 +852,16 @@ public class Utils {
|
||||
|
||||
final String sortValue;
|
||||
switch (preferenceSort) {
|
||||
case BY_TITLE ->
|
||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||
case BY_KEY -> sortValue = preference.getKey();
|
||||
case UNSORTED -> {
|
||||
case BY_TITLE:
|
||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||
break;
|
||||
case BY_KEY:
|
||||
sortValue = preference.getKey();
|
||||
break;
|
||||
case UNSORTED:
|
||||
continue; // Keep original sorting.
|
||||
}
|
||||
default -> throw new IllegalStateException();
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
preferences.put(sortValue, preference);
|
||||
@ -790,7 +871,7 @@ public class Utils {
|
||||
for (Preference pref : preferences.values()) {
|
||||
int order = index++;
|
||||
|
||||
// If the preference is a PreferenceScreen or is an intent preference, move to the top.
|
||||
// Move any screens, intents, and the one off About preference to the top.
|
||||
if (pref instanceof PreferenceScreen || pref.getIntent() != null) {
|
||||
// Arbitrary high number.
|
||||
order -= 1000;
|
||||
@ -799,4 +880,32 @@ public class Utils {
|
||||
pref.setOrder(order);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all preferences to multiline titles if the device is not using an English variant.
|
||||
* The English strings are heavily scrutinized and all titles fit on screen
|
||||
* except 2 or 3 preference strings and those do not affect readability.
|
||||
* <p>
|
||||
* Allowing multiline for those 2 or 3 English preferences looks weird and out of place,
|
||||
* and visually it looks better to clip the text and keep all titles 1 line.
|
||||
*/
|
||||
public static void setPreferenceTitlesToMultiLineIfNeeded(PreferenceGroup group) {
|
||||
if (!isSDKAbove(26)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String revancedLocale = Utils.getContext().getResources().getConfiguration().locale.getLanguage();
|
||||
if (revancedLocale.equals(Locale.ENGLISH.getLanguage())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference pref = group.getPreference(i);
|
||||
pref.setSingleLineTitle(false);
|
||||
|
||||
if (pref instanceof PreferenceGroup subGroup) {
|
||||
setPreferenceTitlesToMultiLineIfNeeded(subGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,14 +189,13 @@ public final class AlternativeThumbnailsPatch {
|
||||
// Unknown tab, treat as the home tab;
|
||||
return homeOption;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.HOME) {
|
||||
return homeOption;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
|
||||
return subscriptionsOption;
|
||||
}
|
||||
// A library tab variant is active.
|
||||
return libraryOption;
|
||||
|
||||
return switch (selectedNavButton) {
|
||||
case SUBSCRIPTIONS, NOTIFICATIONS -> subscriptionsOption;
|
||||
case LIBRARY -> libraryOption;
|
||||
// Home or explore tab.
|
||||
default -> homeOption;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -556,14 +556,13 @@ public final class KeywordContentFilter extends Filter {
|
||||
if (selectedNavButton == null) {
|
||||
return hideHome; // Unknown tab, treat the same as home.
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.HOME) {
|
||||
return hideHome;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||
return hideSubscriptions;
|
||||
}
|
||||
// User is in the Library or Notifications tab.
|
||||
return false;
|
||||
|
||||
return switch (selectedNavButton) {
|
||||
case HOME, EXPLORE -> hideHome;
|
||||
case SUBSCRIPTIONS -> hideSubscriptions;
|
||||
// User is in the Library or notifications.
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private void updateStats(boolean videoWasHidden, @Nullable String keyword) {
|
||||
|
@ -0,0 +1,151 @@
|
||||
package app.revanced.extension.youtube.patches.general;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.PackageUtils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.RootView;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ChangeFormFactorPatch {
|
||||
|
||||
public enum FormFactor {
|
||||
/**
|
||||
* Unmodified, and same as un-patched.
|
||||
*/
|
||||
DEFAULT(null, null, null),
|
||||
/**
|
||||
* <pre>
|
||||
* Some changes include:
|
||||
* - Explore tab is present.
|
||||
* - watch history is missing.
|
||||
* - feed thumbnails fade in.
|
||||
*/
|
||||
UNKNOWN(0, null, null),
|
||||
SMALL(1, null, TRUE),
|
||||
SMALL_WIDTH_DP(1, 480, TRUE),
|
||||
LARGE(2, null, FALSE),
|
||||
LARGE_WIDTH_DP(2, 600, FALSE),
|
||||
/**
|
||||
* Cars with 'Google built-in'.
|
||||
* Layout seems identical to {@link #UNKNOWN}
|
||||
* even when using an Android Automotive device.
|
||||
*/
|
||||
AUTOMOTIVE(3, null, null),
|
||||
WEARABLE(4, null, null);
|
||||
|
||||
@Nullable
|
||||
final Integer formFactorType;
|
||||
|
||||
@Nullable
|
||||
final Integer widthDp;
|
||||
|
||||
@Nullable
|
||||
final Boolean setMinimumDp;
|
||||
|
||||
|
||||
FormFactor(@Nullable Integer formFactorType, @Nullable Integer widthDp, @Nullable Boolean setMinimumDp) {
|
||||
this.formFactorType = formFactorType;
|
||||
this.widthDp = widthDp;
|
||||
this.setMinimumDp = setMinimumDp;
|
||||
}
|
||||
|
||||
private boolean setMinimumDp() {
|
||||
return BooleanUtils.isTrue(setMinimumDp);
|
||||
}
|
||||
}
|
||||
|
||||
private static final FormFactor FORM_FACTOR = Settings.CHANGE_FORM_FACTOR.get();
|
||||
@Nullable
|
||||
private static final Integer FORM_FACTOR_TYPE = FORM_FACTOR.formFactorType;
|
||||
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
|
||||
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getFormFactor(int original) {
|
||||
if (FORM_FACTOR_TYPE == null) return original;
|
||||
|
||||
if (USING_AUTOMOTIVE_TYPE) {
|
||||
// Do not change if the player is opening or is opened,
|
||||
// otherwise the video description cannot be opened.
|
||||
PlayerType current = PlayerType.getCurrent();
|
||||
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
|
||||
Logger.printDebug(() -> "Using original form factor for player");
|
||||
return original;
|
||||
}
|
||||
if (!RootView.isSearchBarActive()) {
|
||||
// Automotive type shows error 400 when opening a channel page and using some explore tab.
|
||||
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
|
||||
// Work around the issue by using the original form factor if not in search and the
|
||||
// navigation back button is present.
|
||||
if (RootView.isBackButtonVisible()) {
|
||||
Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
|
||||
return original;
|
||||
}
|
||||
|
||||
// Do not change library tab otherwise watch history is hidden.
|
||||
// Do this check last since the current navigation button is required.
|
||||
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FORM_FACTOR_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getWidthDp(int original) {
|
||||
if (FORM_FACTOR_TYPE == null) return original;
|
||||
Integer widthDp = FORM_FACTOR.widthDp;
|
||||
if (widthDp == null) {
|
||||
return original;
|
||||
}
|
||||
final int smallestScreenWidthDp = PackageUtils.getSmallestScreenWidthDp();
|
||||
if (smallestScreenWidthDp == 0) {
|
||||
return original;
|
||||
}
|
||||
return FORM_FACTOR.setMinimumDp()
|
||||
? Math.min(smallestScreenWidthDp, widthDp)
|
||||
: Math.max(smallestScreenWidthDp, widthDp);
|
||||
}
|
||||
|
||||
public static boolean phoneLayoutEnabled() {
|
||||
return Objects.equals(FORM_FACTOR.formFactorType, 1);
|
||||
}
|
||||
|
||||
public static boolean tabletLayoutEnabled() {
|
||||
return Objects.equals(FORM_FACTOR.formFactorType, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||
// On first startup of the app the navigation buttons are fetched and updated.
|
||||
// If the user immediately opens the 'You' or opens a video, then the call to
|
||||
// update the navigtation buttons will use the non automotive form factor
|
||||
// and the explore tab is missing.
|
||||
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
||||
// For now, always hide the explore tab.
|
||||
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
|
||||
tabView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package app.revanced.extension.youtube.patches.general;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.utils.PackageUtils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class LayoutSwitchPatch {
|
||||
|
||||
public enum FormFactor {
|
||||
/**
|
||||
* Unmodified type, and same as un-patched.
|
||||
*/
|
||||
ORIGINAL(null, null, null),
|
||||
SMALL_FORM_FACTOR(1, null, TRUE),
|
||||
SMALL_FORM_FACTOR_WIDTH_DP(1, 480, TRUE),
|
||||
LARGE_FORM_FACTOR(2, null, FALSE),
|
||||
LARGE_FORM_FACTOR_WIDTH_DP(2, 600, FALSE);
|
||||
|
||||
@Nullable
|
||||
final Integer formFactorType;
|
||||
|
||||
@Nullable
|
||||
final Integer widthDp;
|
||||
|
||||
@Nullable
|
||||
final Boolean setMinimumDp;
|
||||
|
||||
FormFactor(@Nullable Integer formFactorType, @Nullable Integer widthDp, @Nullable Boolean setMinimumDp) {
|
||||
this.formFactorType = formFactorType;
|
||||
this.widthDp = widthDp;
|
||||
this.setMinimumDp = setMinimumDp;
|
||||
}
|
||||
|
||||
private boolean setMinimumDp() {
|
||||
return BooleanUtils.isTrue(setMinimumDp);
|
||||
}
|
||||
}
|
||||
|
||||
private static final FormFactor FORM_FACTOR = Settings.CHANGE_LAYOUT.get();
|
||||
|
||||
public static int getFormFactor(int original) {
|
||||
Integer formFactorType = FORM_FACTOR.formFactorType;
|
||||
return formFactorType == null
|
||||
? original
|
||||
: formFactorType;
|
||||
}
|
||||
|
||||
public static int getWidthDp(int original) {
|
||||
Integer widthDp = FORM_FACTOR.widthDp;
|
||||
if (widthDp == null) {
|
||||
return original;
|
||||
}
|
||||
final int smallestScreenWidthDp = PackageUtils.getSmallestScreenWidthDp();
|
||||
if (smallestScreenWidthDp == 0) {
|
||||
return original;
|
||||
}
|
||||
return FORM_FACTOR.setMinimumDp()
|
||||
? Math.min(smallestScreenWidthDp, widthDp)
|
||||
: Math.max(smallestScreenWidthDp, widthDp);
|
||||
}
|
||||
|
||||
public static boolean phoneLayoutEnabled() {
|
||||
return Objects.equals(FORM_FACTOR.formFactorType, 1);
|
||||
}
|
||||
|
||||
public static boolean tabletLayoutEnabled() {
|
||||
return Objects.equals(FORM_FACTOR.formFactorType, 2);
|
||||
}
|
||||
|
||||
}
|
@ -32,9 +32,9 @@ import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeT
|
||||
import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||
import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.ThumbnailOption;
|
||||
import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.ThumbnailStillTime;
|
||||
import app.revanced.extension.youtube.patches.general.ChangeFormFactorPatch.FormFactor;
|
||||
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch;
|
||||
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch.StartPage;
|
||||
import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch.FormFactor;
|
||||
import app.revanced.extension.youtube.patches.general.YouTubeMusicActionsPatch;
|
||||
import app.revanced.extension.youtube.patches.player.ExitFullscreenPatch.FullscreenMode;
|
||||
import app.revanced.extension.youtube.patches.player.MiniplayerPatch;
|
||||
@ -154,7 +154,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_GRAY_SEPARATOR = new BooleanSetting("revanced_hide_gray_separator", TRUE);
|
||||
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 EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
||||
public static final BooleanSetting CHANGE_LIVE_RING_CLICK_ACTION = new BooleanSetting("revanced_change_live_ring_click_action", 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));
|
||||
|
@ -75,7 +75,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||
try {
|
||||
if (str == null) return;
|
||||
Setting<?> setting = Setting.getSettingFromPath(str);
|
||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||
|
||||
if (setting == null) return;
|
||||
|
||||
|
@ -8,7 +8,7 @@ import android.preference.Preference;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch;
|
||||
import app.revanced.extension.youtube.patches.general.ChangeFormFactorPatch;
|
||||
import app.revanced.extension.youtube.patches.utils.PatchStatus;
|
||||
import app.revanced.extension.youtube.patches.utils.ReturnYouTubeDislikePatch;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
@ -82,7 +82,7 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
|
||||
*/
|
||||
private static void TabletLayoutLinks() {
|
||||
final boolean isTablet = ExtendedUtils.isTablet() &&
|
||||
!LayoutSwitchPatch.phoneLayoutEnabled();
|
||||
!ChangeFormFactorPatch.phoneLayoutEnabled();
|
||||
|
||||
enableDisablePreferences(
|
||||
isTablet,
|
||||
|
@ -223,6 +223,10 @@ public final class NavigationBar {
|
||||
* This tab will never be in a selected state, even if the create video UI is on screen.
|
||||
*/
|
||||
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
|
||||
/**
|
||||
* Only shown to automotive layout.
|
||||
*/
|
||||
EXPLORE("TAB_EXPLORE"),
|
||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
|
||||
/**
|
||||
* Notifications tab. Only present when
|
||||
|
@ -2,9 +2,48 @@ package app.revanced.extension.youtube.shared;
|
||||
|
||||
import static app.revanced.extension.youtube.patches.components.RelatedVideoFilter.isActionBarVisible;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class RootView {
|
||||
|
||||
/**
|
||||
* Interface to call obfuscated methods in AppCompat Toolbar class.
|
||||
*/
|
||||
public interface AppCompatToolbarPatchInterface {
|
||||
Drawable patch_getToolbarIcon();
|
||||
}
|
||||
|
||||
private static volatile WeakReference<AppCompatToolbarPatchInterface> toolbarResultsRef
|
||||
= new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setToolbar(FrameLayout layout) {
|
||||
AppCompatToolbarPatchInterface toolbar = Utils.getChildView(layout, false, (view) ->
|
||||
view instanceof AppCompatToolbarPatchInterface
|
||||
);
|
||||
|
||||
if (toolbar == null) {
|
||||
Logger.printException(() -> "Could not find toolbar");
|
||||
return;
|
||||
}
|
||||
|
||||
toolbarResultsRef = new WeakReference<>(toolbar);
|
||||
}
|
||||
|
||||
public static boolean isBackButtonVisible() {
|
||||
AppCompatToolbarPatchInterface toolbar = toolbarResultsRef.get();
|
||||
return toolbar != null && toolbar.patch_getToolbarIcon() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the search bar is on screen. This includes if the player
|
||||
* is on screen and the search results are behind the player (and not visible).
|
||||
|
Reference in New Issue
Block a user