mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-28 21:00:19 +02:00
feat(YouTube): Add Change form factor
, Remove Change layout
patch
This commit is contained in:
parent
1b3ebe1a58
commit
86e5c1b45c
@ -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)
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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 ->
|
||||
case BY_TITLE:
|
||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||
case BY_KEY -> sortValue = preference.getKey();
|
||||
case UNSORTED -> {
|
||||
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).
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.general.layoutswitch
|
||||
package app.revanced.patches.youtube.general.formfactor
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
@ -6,30 +6,37 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
|
||||
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.CHANGE_LAYOUT
|
||||
import app.revanced.patches.youtube.utils.navigation.hookNavigationButtonCreated
|
||||
import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.CHANGE_FORM_FACTOR
|
||||
import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
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
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$GENERAL_PATH/LayoutSwitchPatch;"
|
||||
"$GENERAL_PATH/ChangeFormFactorPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val layoutSwitchPatch = bytecodePatch(
|
||||
CHANGE_LAYOUT.title,
|
||||
CHANGE_LAYOUT.summary,
|
||||
val changeFormFactorPatch = bytecodePatch(
|
||||
CHANGE_FORM_FACTOR.title,
|
||||
CHANGE_FORM_FACTOR.summary,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(settingsPatch)
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
playerTypeHookPatch,
|
||||
navigationBarHookPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
@ -53,8 +60,9 @@ val layoutSwitchPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
layoutSwitchFingerprint.methodOrThrow().apply {
|
||||
val index = indexOfFirstInstructionReversedOrThrow(Opcode.IF_NEZ)
|
||||
widthDpUIFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val index = it.patternMatch!!.startIndex
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
@ -64,6 +72,9 @@ val layoutSwitchPatch = bytecodePatch(
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
// region add settings
|
||||
|
||||
@ -71,9 +82,9 @@ val layoutSwitchPatch = bytecodePatch(
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: GENERAL",
|
||||
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
|
||||
"SETTINGS: CHANGE_LAYOUT"
|
||||
"SETTINGS: CHANGE_FORM_FACTOR"
|
||||
),
|
||||
CHANGE_LAYOUT
|
||||
CHANGE_FORM_FACTOR
|
||||
)
|
||||
|
||||
// endregion
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.general.layoutswitch
|
||||
package app.revanced.patches.youtube.general.formfactor
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
@ -11,20 +11,16 @@ internal val formFactorEnumConstructorFingerprint = legacyFingerprint(
|
||||
strings = listOf(
|
||||
"UNKNOWN_FORM_FACTOR",
|
||||
"SMALL_FORM_FACTOR",
|
||||
"LARGE_FORM_FACTOR"
|
||||
"LARGE_FORM_FACTOR",
|
||||
"AUTOMOTIVE_FORM_FACTOR",
|
||||
)
|
||||
)
|
||||
|
||||
internal val layoutSwitchFingerprint = legacyFingerprint(
|
||||
name = "layoutSwitchFingerprint",
|
||||
internal val widthDpUIFingerprint = legacyFingerprint(
|
||||
name = "widthDpUIFingerprint",
|
||||
returnType = "I",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
@ -41,6 +37,11 @@ internal val layoutSwitchFingerprint = legacyFingerprint(
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN
|
||||
Opcode.RETURN,
|
||||
),
|
||||
literals = listOf(
|
||||
480L,
|
||||
600L,
|
||||
720L
|
||||
)
|
||||
)
|
@ -109,8 +109,7 @@ val navigationBarHookPatch = bytecodePatch(
|
||||
hookNavigationButtonCreated = { extensionClassDescriptor ->
|
||||
navigationBarHookCallbackFingerprint.methodOrThrow().addInstruction(
|
||||
0,
|
||||
"invoke-static { p0, p1 }, " +
|
||||
"$extensionClassDescriptor->navigationTabCreated" +
|
||||
"invoke-static { p0, p1 }, $extensionClassDescriptor->navigationTabCreated" +
|
||||
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ internal enum class PatchList(
|
||||
"Bypass URL redirects",
|
||||
"Adds an option to bypass URL redirects and open the original URL directly."
|
||||
),
|
||||
CHANGE_LAYOUT(
|
||||
"Change layout",
|
||||
"Adds an option to change the dp in order to use a tablet or phone layout."
|
||||
CHANGE_FORM_FACTOR(
|
||||
"Change form factor",
|
||||
"Adds an option to change the UI appearance to a phone, tablet, or automotive device."
|
||||
),
|
||||
CHANGE_LIVE_RING_CLICK_ACTION(
|
||||
"Change live ring click action",
|
||||
|
@ -1,6 +1,7 @@
|
||||
package app.revanced.patches.youtube.utils.playertype
|
||||
|
||||
import app.revanced.patches.youtube.utils.resourceid.reelWatchPlayer
|
||||
import app.revanced.patches.youtube.utils.resourceid.toolbarContainerId
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
@ -9,6 +10,7 @@ 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
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
internal val browseIdClassFingerprint = legacyFingerprint(
|
||||
name = "browseIdClassFingerprint",
|
||||
@ -61,6 +63,34 @@ internal val searchQueryClassFingerprint = legacyFingerprint(
|
||||
}
|
||||
)
|
||||
|
||||
internal val toolbarLayoutFingerprint = legacyFingerprint(
|
||||
name = "toolbarLayoutFingerprint",
|
||||
literals = listOf(toolbarContainerId),
|
||||
customFingerprint = { method, _ ->
|
||||
method.name == "<init>" &&
|
||||
indexOfMainCollapsingToolbarLayoutInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfMainCollapsingToolbarLayoutInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.CHECK_CAST &&
|
||||
getReference<TypeReference>()?.type == "Lcom/google/android/apps/youtube/app/ui/actionbar/MainCollapsingToolbarLayout;"
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches to https://android.googlesource.com/platform/frameworks/support/+/9eee6ba/v7/appcompat/src/android/support/v7/widget/Toolbar.java#963
|
||||
*/
|
||||
internal val appCompatToolbarBackButtonFingerprint = legacyFingerprint(
|
||||
name = "appCompatToolbarBackButtonFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Landroid/graphics/drawable/Drawable;",
|
||||
parameters = emptyList(),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.type == "Landroid/support/v7/widget/Toolbar;"
|
||||
},
|
||||
)
|
||||
|
||||
internal val videoStateFingerprint = legacyFingerprint(
|
||||
name = "videoStateFingerprint",
|
||||
returnType = "V",
|
||||
|
@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.shared.litho.addLithoFilter
|
||||
import app.revanced.patches.shared.litho.lithoFilterPatch
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
||||
@ -21,10 +22,13 @@ import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstStringInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
|
||||
private const val EXTENSION_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR =
|
||||
"$UTILS_PATH/PlayerTypeHookPatch;"
|
||||
@ -32,6 +36,9 @@ private const val EXTENSION_PLAYER_TYPE_HOOK_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR =
|
||||
"$SHARED_PATH/RootView;"
|
||||
|
||||
private const val EXTENSION_ROOT_VIEW_TOOLBAR_INTERFACE =
|
||||
"$SHARED_PATH/RootView${'$'}AppCompatToolbarPatchInterface;"
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"$COMPONENTS_PATH/RelatedVideoFilter;"
|
||||
|
||||
@ -165,6 +172,53 @@ val playerTypeHookPatch = bytecodePatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region patch for hook back button visibility
|
||||
|
||||
toolbarLayoutFingerprint.methodOrThrow().apply {
|
||||
val index = indexOfMainCollapsingToolbarLayoutInstruction(this)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"invoke-static { v$register }, $EXTENSION_ROOT_VIEW_HOOK_CLASS_DESCRIPTOR->setToolbar(Landroid/widget/FrameLayout;)V"
|
||||
)
|
||||
}
|
||||
|
||||
// Add interface for extensions code to call obfuscated methods.
|
||||
appCompatToolbarBackButtonFingerprint.matchOrThrow().let {
|
||||
it.classDef.apply {
|
||||
interfaces.add(EXTENSION_ROOT_VIEW_TOOLBAR_INTERFACE)
|
||||
|
||||
val definingClass = type
|
||||
val obfuscatedMethodName = it.originalMethod.name
|
||||
val returnType = "Landroid/graphics/drawable/Drawable;"
|
||||
|
||||
methods.add(
|
||||
ImmutableMethod(
|
||||
definingClass,
|
||||
"patch_getToolbarIcon",
|
||||
listOf(),
|
||||
returnType,
|
||||
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +213,8 @@ var tapBloomView = -1L
|
||||
private set
|
||||
var titleAnchor = -1L
|
||||
private set
|
||||
var toolbarContainerId = -1L
|
||||
private set
|
||||
var toolTipContentView = -1L
|
||||
private set
|
||||
var totalTime = -1L
|
||||
@ -656,6 +658,10 @@ internal val sharedResourceIdPatch = resourcePatch(
|
||||
ID,
|
||||
"title_anchor"
|
||||
]
|
||||
toolbarContainerId = resourceMappings[
|
||||
ID,
|
||||
"toolbar_container"
|
||||
]
|
||||
toolTipContentView = resourceMappings[
|
||||
LAYOUT,
|
||||
"tooltip_content_view"
|
||||
|
@ -22,19 +22,21 @@
|
||||
<item>MIDDLE</item>
|
||||
<item>END</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_change_layout_entries">
|
||||
<item>@string/revanced_change_layout_entry_1</item>
|
||||
<item>@string/revanced_change_layout_entry_2</item>
|
||||
<item>@string/revanced_change_layout_entry_3</item>
|
||||
<item>@string/revanced_change_layout_entry_4</item>
|
||||
<item>@string/revanced_change_layout_entry_5</item>
|
||||
<string-array name="revanced_change_form_factor_entries">
|
||||
<item>@string/revanced_change_form_factor_entry_1</item>
|
||||
<item>@string/revanced_change_form_factor_entry_2</item>
|
||||
<item>@string/revanced_change_form_factor_entry_3</item>
|
||||
<item>@string/revanced_change_form_factor_entry_4</item>
|
||||
<item>@string/revanced_change_form_factor_entry_5</item>
|
||||
<item>@string/revanced_change_form_factor_entry_6</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_change_layout_entry_values">
|
||||
<item>ORIGINAL</item>
|
||||
<item>SMALL_FORM_FACTOR</item>
|
||||
<item>SMALL_FORM_FACTOR_WIDTH_DP</item>
|
||||
<item>LARGE_FORM_FACTOR</item>
|
||||
<item>LARGE_FORM_FACTOR_WIDTH_DP</item>
|
||||
<string-array name="revanced_change_form_factor_entry_values">
|
||||
<item>DEFAULT</item>
|
||||
<item>SMALL</item>
|
||||
<item>SMALL_WIDTH_DP</item>
|
||||
<item>LARGE</item>
|
||||
<item>LARGE_WIDTH_DP</item>
|
||||
<item>AUTOMOTIVE</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_change_start_page_entries">
|
||||
<item>@string/revanced_change_start_page_entry_default</item>
|
||||
|
@ -439,17 +439,27 @@ 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_change_live_ring_click_action_title">Change live ring click action</string>
|
||||
<string name="revanced_change_live_ring_click_action_summary_on">"Channel opens when the live ring is clicked.
|
||||
|
||||
Limitation: When the Shorts live stream is opened in regular player due to the 'Open Shorts in regular player' setting, channel does not open."</string>
|
||||
<string name="revanced_change_live_ring_click_action_summary_off">Live stream opens when the live ring is clicked.</string>
|
||||
<string name="revanced_change_form_factor_title">Layout form factor</string>
|
||||
<string name="revanced_change_form_factor_entry_1">Default</string>
|
||||
<string name="revanced_change_form_factor_entry_2">Phone</string>
|
||||
<string name="revanced_change_form_factor_entry_3">Phone (Max 480 dp)</string>
|
||||
<string name="revanced_change_form_factor_entry_4">Tablet</string>
|
||||
<string name="revanced_change_form_factor_entry_5">Tablet (Min 600 dp)</string>
|
||||
<string name="revanced_change_form_factor_entry_6">Automotive</string>
|
||||
<string name="revanced_change_form_factor_user_dialog_message">"Changes include:
|
||||
|
||||
Tablet layout
|
||||
• Community posts are hidden.
|
||||
|
||||
Automotive layout
|
||||
• Shorts open in the regular player.
|
||||
• Feed is organized by topics and channels.
|
||||
• Video description cannot be opened when 'Spoof streaming data' is turned off."</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>
|
||||
|
@ -269,12 +269,12 @@
|
||||
<!-- PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS
|
||||
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS -->
|
||||
|
||||
<!-- SETTINGS: CHANGE_LAYOUT
|
||||
<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: CHANGE_LAYOUT -->
|
||||
|
||||
<!-- SETTINGS: CHANGE_LIVE_RING_CLICK_ACTION
|
||||
<SwitchPreference android:title="@string/revanced_change_live_ring_click_action_title" android:key="revanced_change_live_ring_click_action" android:summaryOn="@string/revanced_change_live_ring_click_action_summary_on" android:summaryOff="@string/revanced_change_live_ring_click_action_summary_off" />SETTINGS: CHANGE_LIVE_RING_CLICK_ACTION -->
|
||||
|
||||
<!-- SETTINGS: CHANGE_FORM_FACTOR
|
||||
<ListPreference android:entries="@array/revanced_change_form_factor_entries" android:title="@string/revanced_change_form_factor_title" android:key="revanced_change_form_factor" android:entryValues="@array/revanced_change_form_factor_entry_values" />SETTINGS: CHANGE_FORM_FACTOR -->
|
||||
|
||||
<!-- 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" />
|
||||
@ -910,7 +910,7 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/revanced_preference_screen_general_title" android:layout="@layout/revanced_settings_preferences_category">
|
||||
<Preference android:title="Change layout" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Change form factor" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Change live ring click action" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Change start page" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Disable forced auto audio tracks" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user