feat: Add import from & export settings to a file
Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com>
This commit is contained in:
parent
f063cf69bd
commit
1ebd990051
5 changed files with 290 additions and 120 deletions
|
|
@ -2,7 +2,7 @@ package app.revanced.extension.shared.settings;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
@ -122,18 +122,18 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* Called after all settings have been imported.
|
* Called after all settings have been imported.
|
||||||
*/
|
*/
|
||||||
void settingsImported(@Nullable Context context);
|
void settingsImported(@Nullable Activity context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after all settings have been exported.
|
* Called after all settings have been exported.
|
||||||
*/
|
*/
|
||||||
void settingsExported(@Nullable Context context);
|
void settingsExported(@Nullable Activity context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final List<ImportExportCallback> importExportCallbacks = new ArrayList<>();
|
private static final List<ImportExportCallback> importExportCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
|
* Adds a callback for {@link #importFromJSON(Activity, String)} and {@link #exportToJson(Activity)}.
|
||||||
*/
|
*/
|
||||||
public static void addImportExportCallback(ImportExportCallback callback) {
|
public static void addImportExportCallback(ImportExportCallback callback) {
|
||||||
importExportCallbacks.add(Objects.requireNonNull(callback));
|
importExportCallbacks.add(Objects.requireNonNull(callback));
|
||||||
|
|
@ -413,7 +413,7 @@ public abstract class Setting<T> {
|
||||||
json.put(importExportKey, value);
|
json.put(importExportKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String exportToJson(@Nullable Context alertDialogContext) {
|
public static String exportToJson(@Nullable Activity alertDialogContext) {
|
||||||
try {
|
try {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
for (Setting<?> setting : allLoadedSettingsSorted()) {
|
for (Setting<?> setting : allLoadedSettingsSorted()) {
|
||||||
|
|
@ -439,11 +439,17 @@ public abstract class Setting<T> {
|
||||||
|
|
||||||
String export = json.toString(0);
|
String export = json.toString(0);
|
||||||
|
|
||||||
// Remove the outer JSON braces to make the output more compact,
|
if (export.startsWith("{") && export.endsWith("}")) {
|
||||||
// and leave less chance of the user forgetting to copy it
|
// Remove the outer JSON braces to make the output more compact,
|
||||||
return export.substring(2, export.length() - 2);
|
// and leave less chance of the user forgetting to copy it
|
||||||
|
export = export.substring(1, export.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export = export.replaceAll("^\\n+", "").replaceAll("\\n+$", "");
|
||||||
|
|
||||||
|
return export + ",";
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Logger.printException(() -> "Export failure", e); // should never happen
|
Logger.printException(() -> "Export failure", e); // Should never happen
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -451,10 +457,16 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* @return if any settings that require a reboot were changed.
|
* @return if any settings that require a reboot were changed.
|
||||||
*/
|
*/
|
||||||
public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) {
|
public static boolean importFromJSON(Activity alertDialogContext, String settingsJsonString) {
|
||||||
try {
|
try {
|
||||||
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
settingsJsonString = settingsJsonString.trim();
|
||||||
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
|
||||||
|
if (settingsJsonString.endsWith(",")) {
|
||||||
|
settingsJsonString = settingsJsonString.substring(0, settingsJsonString.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settingsJsonString.trim().startsWith("{")) {
|
||||||
|
settingsJsonString = "{\n" + settingsJsonString + "\n}"; // Restore outer JSON braces
|
||||||
}
|
}
|
||||||
JSONObject json = new JSONObject(settingsJsonString);
|
JSONObject json = new JSONObject(settingsJsonString);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package app.revanced.extension.shared.settings.preference;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
|
|
@ -16,11 +18,18 @@ import android.preference.SwitchPreference;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
import app.revanced.extension.shared.ResourceType;
|
||||||
|
|
@ -33,6 +42,9 @@ import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
public static AbstractPreferenceFragment instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that if a preference changes,
|
* Indicates that if a preference changes,
|
||||||
* to apply the change from the Setting to the UI component.
|
* to apply the change from the Setting to the UI component.
|
||||||
|
|
@ -56,6 +68,12 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
@Nullable
|
@Nullable
|
||||||
protected static CharSequence restartDialogTitle, restartDialogMessage, restartDialogButtonText, confirmDialogTitle;
|
protected static CharSequence restartDialogTitle, restartDialogMessage, restartDialogButtonText, confirmDialogTitle;
|
||||||
|
|
||||||
|
private static final int READ_REQUEST_CODE = 42;
|
||||||
|
private static final int WRITE_REQUEST_CODE = 43;
|
||||||
|
private String existingSettings = "";
|
||||||
|
|
||||||
|
private EditText currentImportExportEditText;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
try {
|
try {
|
||||||
if (updatingPreference) {
|
if (updatingPreference) {
|
||||||
|
|
@ -198,8 +216,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
return listPref.getValue().equals(defaultValueString);
|
return listPref.getValue().equals(defaultValueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalStateException("Must override method to handle "
|
throw new IllegalStateException("Must override method to handle preference type: " + pref.getClass());
|
||||||
+ "preference type: " + pref.getClass());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -332,10 +349,230 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
dialogPair.first.show();
|
dialogPair.first.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import / Export Subroutines
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private Button createDialogButton(Context context, String text, int marginLeft, int marginRight, View.OnClickListener listener) {
|
||||||
|
int height = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 36f, context.getResources().getDisplayMetrics());
|
||||||
|
int paddingHorizontal = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 16f, context.getResources().getDisplayMetrics());
|
||||||
|
float radius = android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 20f, context.getResources().getDisplayMetrics());
|
||||||
|
|
||||||
|
Button btn = new Button(context, null, 0);
|
||||||
|
btn.setText(text);
|
||||||
|
btn.setAllCaps(false);
|
||||||
|
btn.setTextSize(14);
|
||||||
|
btn.setSingleLine(true);
|
||||||
|
btn.setEllipsize(android.text.TextUtils.TruncateAt.END);
|
||||||
|
btn.setGravity(android.view.Gravity.CENTER);
|
||||||
|
btn.setPadding(paddingHorizontal, 0, paddingHorizontal, 0);
|
||||||
|
btn.setTextColor(Utils.isDarkModeEnabled() ? android.graphics.Color.WHITE : android.graphics.Color.BLACK);
|
||||||
|
|
||||||
|
android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable();
|
||||||
|
bg.setCornerRadius(radius);
|
||||||
|
bg.setColor(Utils.getCancelOrNeutralButtonBackgroundColor());
|
||||||
|
btn.setBackground(bg);
|
||||||
|
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, height, 1.0f);
|
||||||
|
params.setMargins(marginLeft, 0, marginRight, 0);
|
||||||
|
btn.setLayoutParams(params);
|
||||||
|
btn.setOnClickListener(listener);
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
public void showImportExportTextDialog() {
|
||||||
|
try {
|
||||||
|
Activity context = getActivity();
|
||||||
|
// Must set text before showing dialog,
|
||||||
|
// otherwise text is non-selectable if this preference is later reopened.
|
||||||
|
existingSettings = Setting.exportToJson(context);
|
||||||
|
currentImportExportEditText = getEditText(context);
|
||||||
|
|
||||||
|
// Create a custom dialog with the EditText.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
|
context,
|
||||||
|
str("revanced_pref_import_export_title"), // Title.
|
||||||
|
null, // No message (EditText replaces it).
|
||||||
|
currentImportExportEditText, // Pass the EditText.
|
||||||
|
str("revanced_settings_save"), // OK button text.
|
||||||
|
() -> importSettingsText(context, currentImportExportEditText.getText().toString()), // OK button action.
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
|
||||||
|
() -> Utils.setClipboard(currentImportExportEditText.getText().toString()), // Neutral button (Copy) action. Show the user the settings in JSON format.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
LinearLayout fileButtonsContainer = getLinearLayout(context);
|
||||||
|
int margin = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 4f, context.getResources().getDisplayMetrics());
|
||||||
|
|
||||||
|
Button btnExport = createDialogButton(context, str("revanced_settings_export_file"), 0, margin, v -> exportActivity());
|
||||||
|
Button btnImport = createDialogButton(context, str("revanced_settings_import_file"), margin, 0, v -> importActivity());
|
||||||
|
|
||||||
|
fileButtonsContainer.addView(btnExport);
|
||||||
|
fileButtonsContainer.addView(btnImport);
|
||||||
|
|
||||||
|
dialogPair.second.addView(fileButtonsContainer, 2);
|
||||||
|
|
||||||
|
dialogPair.first.setOnDismissListener(d -> currentImportExportEditText = null);
|
||||||
|
|
||||||
|
// If there are no settings yet, then show the on-screen keyboard and bring focus to
|
||||||
|
// the edit text. This makes it easier to paste saved settings after a reinstallation.
|
||||||
|
dialogPair.first.setOnShowListener(dialogInterface -> {
|
||||||
|
if (existingSettings.isEmpty() && currentImportExportEditText != null) {
|
||||||
|
currentImportExportEditText.postDelayed(() -> {
|
||||||
|
if (currentImportExportEditText != null) {
|
||||||
|
currentImportExportEditText.requestFocus();
|
||||||
|
android.view.inputmethod.InputMethodManager imm = (android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null) imm.showSoftInput(currentImportExportEditText, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "showImportExportTextDialog failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static LinearLayout getLinearLayout(Context context) {
|
||||||
|
LinearLayout fileButtonsContainer = new LinearLayout(context);
|
||||||
|
fileButtonsContainer.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
LinearLayout.LayoutParams fbParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
|
int marginTop = (int) android.util.TypedValue.applyDimension(android.util.TypedValue.COMPLEX_UNIT_DIP, 16f, context.getResources().getDisplayMetrics());
|
||||||
|
fbParams.setMargins(0, marginTop, 0, 0);
|
||||||
|
fileButtonsContainer.setLayoutParams(fbParams);
|
||||||
|
return fileButtonsContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private EditText getEditText(Context context) {
|
||||||
|
EditText editText = new EditText(context);
|
||||||
|
editText.setText(existingSettings);
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
|
editText.setAutofillHints((String) null);
|
||||||
|
}
|
||||||
|
editText.setInputType(android.text.InputType.TYPE_CLASS_TEXT |
|
||||||
|
android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS |
|
||||||
|
android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||||
|
editText.setSingleLine(false);
|
||||||
|
editText.setTextSize(14);
|
||||||
|
return editText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportActivity() {
|
||||||
|
try {
|
||||||
|
Setting.exportToJson(getActivity());
|
||||||
|
|
||||||
|
String formatDate = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US).format(new java.util.Date());
|
||||||
|
String fileName = "revanced_Settings_" + formatDate + ".txt";
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
intent.putExtra(Intent.EXTRA_TITLE, fileName);
|
||||||
|
startActivityForResult(intent, WRITE_REQUEST_CODE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "exportActivity failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importActivity() {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("*/*");
|
||||||
|
startActivityForResult(intent, READ_REQUEST_CODE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "importActivity failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == WRITE_REQUEST_CODE && resultCode == android.app.Activity.RESULT_OK && data != null) {
|
||||||
|
exportTextToFile(data.getData());
|
||||||
|
} else if (requestCode == READ_REQUEST_CODE && resultCode == android.app.Activity.RESULT_OK && data != null) {
|
||||||
|
importTextFromFile(data.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void showLocalizedToast(String resourceKey, String fallbackMessage) {
|
||||||
|
if (ResourceUtils.getIdentifier(ResourceType.STRING, resourceKey) != 0) {
|
||||||
|
Utils.showToastLong(str(resourceKey));
|
||||||
|
} else {
|
||||||
|
Utils.showToastLong(fallbackMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportTextToFile(android.net.Uri uri) {
|
||||||
|
try {
|
||||||
|
OutputStream out = getContext().getContentResolver().openOutputStream(uri);
|
||||||
|
if (out != null) {
|
||||||
|
String textToExport = existingSettings;
|
||||||
|
if (currentImportExportEditText != null) {
|
||||||
|
textToExport = currentImportExportEditText.getText().toString();
|
||||||
|
}
|
||||||
|
out.write(textToExport.getBytes(StandardCharsets.UTF_8));
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
showLocalizedToast("revanced_settings_export_file_success", "Settings exported successfully");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
showLocalizedToast("revanced_settings_export_file_failed", "Failed to export settings");
|
||||||
|
Logger.printException(() -> "exportTextToFile failure", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||||
|
private void importTextFromFile(android.net.Uri uri) {
|
||||||
|
try {
|
||||||
|
InputStream in = getContext().getContentResolver().openInputStream(uri);
|
||||||
|
if (in != null) {
|
||||||
|
Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name()).useDelimiter("\\A");
|
||||||
|
String result = scanner.hasNext() ? scanner.next() : "";
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
if (currentImportExportEditText != null) {
|
||||||
|
currentImportExportEditText.setText(result);
|
||||||
|
showLocalizedToast("revanced_settings_import_file_success", "Settings imported successfully, tap Save to apply");
|
||||||
|
} else {
|
||||||
|
importSettingsText(getContext(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
showLocalizedToast("revanced_settings_import_file_failed", "Failed to import settings");
|
||||||
|
Logger.printException(() -> "importTextFromFile failure", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importSettingsText(Context context, String replacementSettings) {
|
||||||
|
try {
|
||||||
|
existingSettings = Setting.exportToJson(null);
|
||||||
|
if (replacementSettings.equals(existingSettings)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settingImportInProgress = true;
|
||||||
|
final boolean rebootNeeded = Setting.importFromJSON(getActivity(), replacementSettings);
|
||||||
|
if (rebootNeeded) {
|
||||||
|
showRestartDialog(context);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "importSettingsText failure", ex);
|
||||||
|
} finally {
|
||||||
|
settingImportInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
@SuppressLint("ResourceType")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
instance = this;
|
||||||
try {
|
try {
|
||||||
PreferenceManager preferenceManager = getPreferenceManager();
|
PreferenceManager preferenceManager = getPreferenceManager();
|
||||||
preferenceManager.setSharedPreferencesName(Setting.preferences.name);
|
preferenceManager.setSharedPreferencesName(Setting.preferences.name);
|
||||||
|
|
@ -354,6 +591,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
if (instance == this) {
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
|
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,13 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.EditTextPreference;
|
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.text.InputType;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
public class ImportExportPreference extends Preference implements Preference.OnPreferenceClickListener {
|
||||||
|
|
||||||
private String existingSettings;
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
setSelectable(true);
|
|
||||||
|
|
||||||
EditText editText = getEditText();
|
|
||||||
editText.setTextIsSelectable(true);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
editText.setAutofillHints((String) null);
|
|
||||||
}
|
|
||||||
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
|
||||||
editText.setTextSize(14);
|
|
||||||
|
|
||||||
setOnPreferenceClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImportExportPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ImportExportPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
|
@ -56,78 +29,20 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setOnPreferenceClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
try {
|
try {
|
||||||
// Must set text before showing dialog,
|
if (AbstractPreferenceFragment.instance != null) {
|
||||||
// otherwise text is non-selectable if this preference is later reopened.
|
AbstractPreferenceFragment.instance.showImportExportTextDialog();
|
||||||
existingSettings = Setting.exportToJson(getContext());
|
}
|
||||||
getEditText().setText(existingSettings);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "showDialog failure", ex);
|
Logger.printException(() -> "onPreferenceClick failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void showDialog(Bundle state) {
|
|
||||||
try {
|
|
||||||
Context context = getContext();
|
|
||||||
EditText editText = getEditText();
|
|
||||||
|
|
||||||
// Create a custom dialog with the EditText.
|
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
|
||||||
context,
|
|
||||||
str("revanced_pref_import_export_title"), // Title.
|
|
||||||
null, // No message (EditText replaces it).
|
|
||||||
editText, // Pass the EditText.
|
|
||||||
str("revanced_settings_import"), // OK button text.
|
|
||||||
() -> importSettings(context, editText.getText().toString()), // OK button action.
|
|
||||||
() -> {}, // Cancel button action (dismiss only).
|
|
||||||
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
|
|
||||||
() -> {
|
|
||||||
// Neutral button (Copy) action. Show the user the settings in JSON format.
|
|
||||||
Utils.setClipboard(editText.getText());
|
|
||||||
},
|
|
||||||
true // Dismiss dialog when onNeutralClick.
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there are no settings yet, then show the on screen keyboard and bring focus to
|
|
||||||
// the edit text. This makes it easier to paste saved settings after a reinstall.
|
|
||||||
dialogPair.first.setOnShowListener(dialogInterface -> {
|
|
||||||
if (existingSettings.isEmpty()) {
|
|
||||||
editText.postDelayed(() -> {
|
|
||||||
editText.requestFocus();
|
|
||||||
|
|
||||||
InputMethodManager inputMethodManager = (InputMethodManager)
|
|
||||||
editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show the dialog.
|
|
||||||
dialogPair.first.show();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "showDialog failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void importSettings(Context context, String replacementSettings) {
|
|
||||||
try {
|
|
||||||
if (replacementSettings.equals(existingSettings)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AbstractPreferenceFragment.settingImportInProgress = true;
|
|
||||||
|
|
||||||
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
|
|
||||||
if (rebootNeeded) {
|
|
||||||
AbstractPreferenceFragment.showRestartDialog(context);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "importSettings failure", ex);
|
|
||||||
} finally {
|
|
||||||
AbstractPreferenceFragment.settingImportInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package app.revanced.extension.youtube.sponsorblock;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
@ -34,12 +34,12 @@ public class SponsorBlockSettings {
|
||||||
|
|
||||||
public static final Setting.ImportExportCallback SB_IMPORT_EXPORT_CALLBACK = new Setting.ImportExportCallback() {
|
public static final Setting.ImportExportCallback SB_IMPORT_EXPORT_CALLBACK = new Setting.ImportExportCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void settingsImported(@Nullable Context context) {
|
public void settingsImported(@Nullable Activity context) {
|
||||||
SegmentCategory.loadAllCategoriesFromSettings();
|
SegmentCategory.loadAllCategoriesFromSettings();
|
||||||
SponsorBlockPreferenceGroup.settingsImported = true;
|
SponsorBlockPreferenceGroup.settingsImported = true;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void settingsExported(@Nullable Context context) {
|
public void settingsExported(@Nullable Activity context) {
|
||||||
showExportWarningIfNeeded(context);
|
showExportWarningIfNeeded(context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -184,16 +184,16 @@ public class SponsorBlockSettings {
|
||||||
/**
|
/**
|
||||||
* Export the categories using flatten JSON (no embedded dictionaries or arrays).
|
* Export the categories using flatten JSON (no embedded dictionaries or arrays).
|
||||||
*/
|
*/
|
||||||
private static void showExportWarningIfNeeded(@Nullable Context dialogContext) {
|
private static void showExportWarningIfNeeded(@Nullable Activity activity) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
initialize();
|
initialize();
|
||||||
|
|
||||||
// If user has a SponsorBlock user ID then show a warning.
|
// If user has a SponsorBlock user ID then show a warning.
|
||||||
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateID()
|
if (activity != null && SponsorBlockSettings.userHasSBPrivateID()
|
||||||
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
||||||
// Create the custom dialog.
|
// Create the custom dialog.
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
dialogContext,
|
activity,
|
||||||
null, // No title.
|
null, // No title.
|
||||||
str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
|
str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
|
||||||
null, // No EditText.
|
null, // No EditText.
|
||||||
|
|
@ -205,11 +205,7 @@ public class SponsorBlockSettings {
|
||||||
true // Dismiss dialog when onNeutralClick.
|
true // Dismiss dialog when onNeutralClick.
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set dialog as non-cancelable.
|
Utils.showDialog(activity, dialogPair.first, false, null);
|
||||||
dialogPair.first.setCancelable(false);
|
|
||||||
|
|
||||||
// Show the dialog.
|
|
||||||
dialogPair.first.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,13 @@ To translate new languages or improve the existing translations, visit translate
|
||||||
<string name="revanced_language_DEFAULT">App language</string>
|
<string name="revanced_language_DEFAULT">App language</string>
|
||||||
<string name="revanced_pref_import_export_title">Import / Export</string>
|
<string name="revanced_pref_import_export_title">Import / Export</string>
|
||||||
<string name="revanced_pref_import_export_summary">Import / Export ReVanced settings</string>
|
<string name="revanced_pref_import_export_summary">Import / Export ReVanced settings</string>
|
||||||
|
<string name="revanced_settings_import_file">Import from file</string>
|
||||||
|
<string name="revanced_settings_import_file_success">Settings imported successfully, save to apply</string>
|
||||||
|
<string name="revanced_settings_import_file_failed">Failed to import settings</string>
|
||||||
|
<string name="revanced_settings_export_file">Export to file</string>
|
||||||
|
<string name="revanced_settings_export_file_success">Settings exported successfully</string>
|
||||||
|
<string name="revanced_settings_export_file_failed">Failed to export settings</string>
|
||||||
|
|
||||||
<!-- Settings about dialog. -->
|
<!-- Settings about dialog. -->
|
||||||
<string name="revanced_settings_about_links_body">You are using ReVanced Patches version <i>%s</i></string>
|
<string name="revanced_settings_about_links_body">You are using ReVanced Patches version <i>%s</i></string>
|
||||||
<string name="revanced_settings_about_links_dev_header">Note</string>
|
<string name="revanced_settings_about_links_dev_header">Note</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue