259 lines
11 KiB
Java
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();
|
|
}
|
|
}
|