revanced-integrations/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment....

259 lines
11 KiB
Java

package app.revanced.integrations.shared.settings.preference;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.shared.Utils;
import app.revanced.integrations.shared.settings.BooleanSetting;
import app.revanced.integrations.shared.settings.Setting;
import static app.revanced.integrations.shared.StringRef.str;
/**
*
*
* @noinspection deprecation, DataFlowIssue , unused */
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
/**
* Indicates that if a preference changes,
* to apply the change from the Setting to the UI component.
*/
public static boolean settingImportInProgress;
/**
* Confirm and restart dialog button text and title.
* Set by subclasses if Strings cannot be added as a resource.
*/
@Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
/**
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
*/
private boolean showingUserDialogMessage;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try {
Setting<?> setting = Setting.getSettingFromPath(str);
if (setting == null) {
return;
}
Preference pref = findPreference(str);
if (pref == null) {
return;
}
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);
} else if (setting.rebootApp) {
showRestartDialog(getContext());
}
}
} catch (Exception ex) {
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
}
};
/**
* Initialize this instance, and do any custom behavior.
* <p>
* To ensure all {@link Setting} instances are correctly synced to the UI,
* it is important that subclasses make a call or otherwise reference their Settings class bundle
* so all app specific {@link Setting} instances are loaded before this method returns.
*/
protected void initialize() {
final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml");
if (identifier == 0) return;
addPreferencesFromResource(identifier);
Utils.sortPreferenceGroups(getPreferenceScreen());
}
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
Utils.verifyOnMainThread();
final var context = getContext();
if (confirmDialogTitle == null) {
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
}
showingUserDialogMessage = true;
new AlertDialog.Builder(context)
.setTitle(confirmDialogTitle)
.setMessage(setting.userDialogMessage.toString())
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
if (setting.rebootApp) {
showRestartDialog(context);
}
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
})
.setOnDismissListener(dialog -> {
showingUserDialogMessage = false;
})
.setCancelable(false)
.show();
}
/**
* Updates all Preferences values and their availability using the current values in {@link Setting}.
*/
protected void updateUIToSettingValues() {
updatePreferenceScreen(getPreferenceScreen(), true,true);
}
/**
* Updates Preferences availability only using the status of {@link Setting}.
*/
protected void updateUIAvailability() {
updatePreferenceScreen(getPreferenceScreen(), false, false);
}
/**
* Syncs all UI Preferences to any {@link Setting} they represent.
*/
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
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) {
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference);
} else if (pref.hasKey()) {
String key = pref.getKey();
Setting<?> setting = Setting.getSettingFromPath(key);
if (setting != null) {
updatePreference(pref, setting, syncSettingValue, applySettingToPreference);
}
}
}
}
/**
* Updates a UI Preference with the {@link Setting} that backs it.
* If needed, subclasses can override this to handle additional UI Preference types.
*
* @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.
*/
protected void updatePreference(@NonNull Preference pref, @NonNull Setting<?> setting,
boolean syncSetting, boolean applySettingToPreference) {
if (!syncSetting && applySettingToPreference) {
throw new IllegalArgumentException();
}
if (syncSetting) {
if (pref instanceof SwitchPreference) {
SwitchPreference switchPref = (SwitchPreference) pref;
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;
if (applySettingToPreference) {
editPreference.setText(setting.get().toString());
} else {
Setting.privateSetValueFromString(setting, editPreference.getText());
}
} else if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
if (applySettingToPreference) {
listPref.setValue(setting.get().toString());
} else {
Setting.privateSetValueFromString(setting, listPref.getValue());
}
updateListPreferenceSummary(listPref, setting);
} else {
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
return;
}
}
updatePreferenceAvailability(pref, setting);
}
protected void updatePreferenceAvailability(@NonNull Preference pref, @NonNull Setting<?> setting) {
pref.setEnabled(setting.isAvailable());
}
protected void updateListPreferenceSummary(ListPreference listPreference, Setting<?> setting) {
String objectStringValue = setting.get().toString();
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
if (entryIndex >= 0) {
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
} else {
// Value is not an available option.
// User manually edited import data, or options changed and current selection is no longer available.
// Still show the value in the summary, so it's clear that something is selected.
listPreference.setSummary(objectStringValue);
}
}
public static void showRestartDialog(@NonNull final Context context) {
Utils.verifyOnMainThread();
if (restartDialogTitle == null) {
restartDialogTitle = str("revanced_settings_restart_title");
}
if (restartDialogButtonText == null) {
restartDialogButtonText = str("revanced_settings_restart");
}
new AlertDialog.Builder(context)
.setMessage(restartDialogTitle)
.setPositiveButton(restartDialogButtonText, (dialog, id)
-> Utils.restartApp(context))
.setNegativeButton(android.R.string.cancel, null)
.setCancelable(false)
.show();
}
@SuppressLint("ResourceType")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName(Setting.preferences.name);
// Must initialize before adding change listener,
// otherwise the syncing of Setting -> UI
// causes a callback to the listener even though nothing changed.
initialize();
updateUIToSettingValues();
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
} catch (Exception ex) {
Logger.printException(() -> "onCreate() failure", ex);
}
}
@Override
public void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
super.onDestroy();
}
}