mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-12 13:17:38 +02:00
feat(YouTube): Add Change form factor
patch (#4217)
This commit is contained in:

committed by
GitHub

parent
da4aeac42b
commit
644ac5baa6
@ -153,7 +153,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;
|
||||
@ -244,6 +243,7 @@ public abstract class Setting<T> {
|
||||
*
|
||||
* 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.
|
||||
@ -419,6 +419,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)) {
|
||||
|
@ -42,7 +42,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||
try {
|
||||
Setting<?> setting = Setting.getSettingFromPath(str);
|
||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||
if (setting == null) {
|
||||
return;
|
||||
}
|
||||
@ -52,23 +52,21 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
Logger.printDebug(() -> "Preference changed: " + setting.key);
|
||||
|
||||
// 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);
|
||||
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();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||
}
|
||||
@ -92,7 +90,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
|
||||
}
|
||||
|
||||
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
|
||||
private void showSettingUserDialogConfirmation(Preference pref, Setting<?> setting) {
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
final var context = getContext();
|
||||
@ -104,12 +102,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
.setTitle(confirmDialogTitle)
|
||||
.setMessage(Objects.requireNonNull(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;
|
||||
@ -132,6 +137,24 @@ 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.
|
||||
*/
|
||||
@ -170,23 +193,20 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
protected void syncSettingWithPreference(@NonNull Preference pref,
|
||||
@NonNull Setting<?> setting,
|
||||
boolean applySettingToPreference) {
|
||||
if (pref instanceof SwitchPreference) {
|
||||
SwitchPreference switchPref = (SwitchPreference) pref;
|
||||
if (pref instanceof SwitchPreference switchPref) {
|
||||
BooleanSetting boolSetting = (BooleanSetting) setting;
|
||||
if (applySettingToPreference) {
|
||||
switchPref.setChecked(boolSetting.get());
|
||||
} else {
|
||||
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
||||
}
|
||||
} else if (pref instanceof EditTextPreference) {
|
||||
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||
} else if (pref instanceof EditTextPreference editPreference) {
|
||||
if (applySettingToPreference) {
|
||||
editPreference.setText(setting.get().toString());
|
||||
} else {
|
||||
Setting.privateSetValueFromString(setting, editPreference.getText());
|
||||
}
|
||||
} else if (pref instanceof ListPreference) {
|
||||
ListPreference listPref = (ListPreference) pref;
|
||||
} else if (pref instanceof ListPreference listPref) {
|
||||
if (applySettingToPreference) {
|
||||
listPref.setValue(setting.get().toString());
|
||||
} else {
|
||||
|
@ -176,14 +176,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;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,54 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ChangeFormFactorPatch {
|
||||
|
||||
public enum FormFactor {
|
||||
/**
|
||||
* Unmodified, and same as un-patched.
|
||||
*/
|
||||
DEFAULT(null),
|
||||
/**
|
||||
* <pre>
|
||||
* Some changes include:
|
||||
* - Explore tab is present.
|
||||
* - watch history is missing.
|
||||
* - feed thumbnails fade in.
|
||||
*/
|
||||
UNKNOWN(0),
|
||||
SMALL(1),
|
||||
LARGE(2),
|
||||
/**
|
||||
* Cars with 'Google built-in'.
|
||||
* Layout seems identical to {@link #UNKNOWN}
|
||||
* even when using an Android Automotive device.
|
||||
*/
|
||||
AUTOMOTIVE(3),
|
||||
WEARABLE(4);
|
||||
|
||||
@Nullable
|
||||
final Integer formFactorType;
|
||||
|
||||
FormFactor(@Nullable Integer formFactorType) {
|
||||
this.formFactorType = formFactorType;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getFormFactor(int original) {
|
||||
return FORM_FACTOR_TYPE == null
|
||||
? original
|
||||
: FORM_FACTOR_TYPE;
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class TabletLayoutPatch {
|
||||
|
||||
private static final boolean TABLET_LAYOUT_ENABLED = Settings.TABLET_LAYOUT.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean getTabletLayoutEnabled() {
|
||||
return TABLET_LAYOUT_ENABLED;
|
||||
}
|
||||
}
|
@ -528,14 +528,13 @@ 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) {
|
||||
|
@ -366,7 +366,7 @@ public final class ShortsFilter extends Filter {
|
||||
}
|
||||
|
||||
return switch (selectedNavButton) {
|
||||
case HOME -> hideHome;
|
||||
case HOME, EXPLORE -> hideHome;
|
||||
case SUBSCRIPTIONS -> hideSubscriptions;
|
||||
case LIBRARY -> hideHistory;
|
||||
default -> false;
|
||||
|
@ -7,6 +7,7 @@ import static app.revanced.extension.shared.settings.Setting.migrateFromOldPrefe
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
||||
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
||||
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
|
||||
@ -202,12 +203,12 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
||||
|
||||
// General layout
|
||||
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 BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||
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 BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
|
||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION));
|
||||
|
@ -3,12 +3,15 @@ package app.revanced.extension.youtube.shared;
|
||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
@ -242,6 +245,30 @@ public final class NavigationBar {
|
||||
// Code is added during patching.
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the bundled non cairo filled icon instead of a custom icon.
|
||||
* Use the old non cairo filled icon, which is almost identical to
|
||||
* the what would be the filled cairo icon.
|
||||
*/
|
||||
private static final int fillBellCairoBlack = Utils.getResourceIdentifier(
|
||||
"yt_fill_bell_black_24", "drawable");
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Fixes missing drawable.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
|
||||
if (fillBellCairoBlack != 0) {
|
||||
// Show a popup informing this fix is no longer needed to those who might care.
|
||||
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
|
||||
Logger.printException(() -> "YouTube fixed the cairo notification icons");
|
||||
}
|
||||
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
|
||||
}
|
||||
}
|
||||
|
||||
public enum NavigationButton {
|
||||
HOME("PIVOT_HOME", "TAB_HOME_CAIRO"),
|
||||
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
|
||||
@ -250,6 +277,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
|
||||
|
Reference in New Issue
Block a user