diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a028363..495fe02 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ + ) getIntent().getSerializableExtra(EXTRA_CLASS)).getConstructor(Context.class).newInstance(this); + } catch (Exception e) { + AndroidUtils.assertError("Unable to instantiate the JsonEditorInterface", e); + Toast.makeText(this, R.string.toast_invalid, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + info = findViewById(R.id.info); + this.findViewById(R.id.description).setText(provider.getEditorDescription()); + + editor = findViewById(R.id.data); + editor.setText(noFailToString(provider.getJson())); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_json_editor, menu); + AndroidUtils.fixMenuIconColor(menu.findItem(R.id.menu_format), this); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // press the 'back' button in the action bar to go back + case android.R.id.home -> onBackPressed(); + // format content + case R.id.menu_format -> format(); + // save + case R.id.menu_save -> save(); + // discard + case R.id.menu_discard -> discard(); + // reset + case R.id.menu_reset -> reset(); + // toggle info visibility + case R.id.menu_show_info -> { + item.setChecked(!item.isChecked()); + info.setVisibility(item.isChecked() ? View.VISIBLE : View.GONE); + } + + default -> { + return super.onOptionsItemSelected(item); + } + } + return true; + } + + @Override + public void onBackPressed() { + // validate + JSONObject currentData; + try { + currentData = new JSONObject(editor.getText().toString()); + } catch (JSONException e) { + new AlertDialog.Builder(this) + .setTitle(R.string.toast_invalid) + .setMessage(R.string.json_ignore) + .setPositiveButton(R.string.json_discard_close, (dialog, which) -> finish()) + .setNegativeButton(android.R.string.cancel, null) + .show(); + return; + } + + // check changes, exit if there are none + if (Objects.equals(noFailToString(currentData), noFailToString(provider.getJson()))) { + finish(); + return; + } + + // ask to save or discard + new AlertDialog.Builder(this) + .setTitle(R.string.save) + .setMessage(R.string.json_save) + .setPositiveButton(R.string.save, (dialog, which) -> { + var result = provider.saveJson(currentData); + if (result == null) { + // all ok, exit + finish(); + } else { + // error while saving + new AlertDialog.Builder(this) + .setTitle(R.string.toast_invalid) + .setMessage(getString(R.string.json_save_error, result)) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + }) + .setNeutralButton(R.string.discard, (dialog, which) -> finish()) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + /* ------------------- actions ------------------- */ + + /** Formats the editor content, shows a dialog if invalid */ + private void format() { + try { + editor.setText(new JSONObject(editor.getText().toString()).toString(INDENT_SPACES)); + } catch (JSONException e) { + // invalid json + new AlertDialog.Builder(this) + .setTitle(R.string.toast_invalid) + .setMessage(R.string.json_format_error) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + } + + /** Discard the editor changes and restore the saved contents */ + private void discard() { + new AlertDialog.Builder(this) + .setTitle(R.string.discard) + .setMessage(R.string.json_discard) + .setPositiveButton(android.R.string.yes, (dialog, which) -> editor.setText(noFailToString(provider.getJson()))) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + /** Discard the editor changes and restore the built-in contents */ + private void reset() { + new AlertDialog.Builder(this) + .setTitle(R.string.reset) + .setMessage(R.string.json_reset) + .setPositiveButton(android.R.string.yes, (dialog, which) -> editor.setText(noFailToString(provider.getBuiltInJson()))) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + /** Saved current content. Display a dialog if it's not valid json or the saving failed */ + private void save() { + String result; + try { + result = provider.saveJson(new JSONObject(editor.getText().toString())); + } catch (JSONException e) { + // invalid json + result = getString(R.string.json_format_error); + } + + if (result != null) { + // error while saving + new AlertDialog.Builder(this) + .setTitle(R.string.toast_invalid) + .setMessage(getString(R.string.json_save_error, result)) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + } + + /* ------------------- utils ------------------- */ + + /** Formats a json into a valid string that doesn't throws a JSON exception */ + private static String noFailToString(JSONObject content) { + try { + return content.toString(INDENT_SPACES); + } catch (JSONException e) { + // panic, don't format then + return content.toString(); + } + } + +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/activities/JsonEditorInterface.java b/app/src/main/java/com/trianguloy/urlchecker/activities/JsonEditorInterface.java new file mode 100644 index 0000000..9b23935 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/activities/JsonEditorInterface.java @@ -0,0 +1,37 @@ +package com.trianguloy.urlchecker.activities; + +import static com.trianguloy.urlchecker.activities.JsonEditorActivity.EXTRA_CLASS; + +import android.content.Context; +import android.content.Intent; + +import org.json.JSONObject; + +/** + * If a class implements this, it will be able to show a JSON editor. + * IMPORTANT! The class must also have a constructor with a single Context parameter. + */ +public interface JsonEditorInterface { + + /* ------------------------- implementation ------------------------- */ + + /** Should return the currently saved json */ + JSONObject getJson(); + + /** Should return the built-in json */ + JSONObject getBuiltInJson(); + + /** Should saves a json. Return null if all ok, a message string to display if not */ + String saveJson(JSONObject data); + + /** The string id of the description to show in the editor */ + int getEditorDescription(); + + /* ------------------------- usage ------------------------- */ + + /** Displays a generic editor for json content. */ + default void showEditor(Context cntx) { + cntx.startActivity(new Intent(cntx, JsonEditorActivity.class) + .putExtra(EXTRA_CLASS, this.getClass())); + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java b/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java deleted file mode 100644 index 32dd33d..0000000 --- a/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.trianguloy.urlchecker.dialogs; - -import android.app.Activity; -import android.app.AlertDialog; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import com.trianguloy.urlchecker.R; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * @see this#show - */ -public class JsonEditor { - - public interface Listener { - /** - * Current data that the user wants to save. - * Return true if it is ok and the dialog should be dismissed, false otherwise. - */ - boolean onSave(JSONObject content); - } - - /** - * Displays a generic editor for json content. - */ - public static void show(JSONObject content, JSONObject reset, int description, Activity cntx, Listener onSave) { - - // prepare dialog content - View views = cntx.getLayoutInflater().inflate(R.layout.json_editor, null); - views.findViewById(R.id.description).setText(description); - - // init rules - EditText data = views.findViewById(R.id.data); - data.setText(noFailToString(content)); - - // formatter - views.findViewById(R.id.format).setOnClickListener(v -> { - try { - data.setText(new JSONObject(data.getText().toString()).toString(2)); - } catch (JSONException e) { - e.printStackTrace(); - Toast.makeText(cntx, R.string.toast_invalid, Toast.LENGTH_SHORT).show(); - } - }); - - // prepare dialog - AlertDialog dialog = new AlertDialog.Builder(cntx) - .setView(views) - .setPositiveButton(R.string.save, null) // set below - .setNegativeButton(android.R.string.cancel, null) - .setNeutralButton(R.string.reset, null) // set below - .setCancelable(false) - .show(); - - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - - // prepare more dialog - // these are configured here to disable automatic auto-closing when they are pressed - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - try { - if (onSave.onSave(new JSONObject(data.getText().toString()))) { - dialog.dismiss(); - } - } catch (JSONException e) { - e.printStackTrace(); - Toast.makeText(cntx, R.string.toast_invalid, Toast.LENGTH_SHORT).show(); - } - }); - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { - // clear catalog and reload internal - data.setText(noFailToString(reset)); - }); - } - - /** - * Formats a json into a valid string that doesn't throws a JSON exception - * (as a function because I needed a final string) - */ - private static String noFailToString(JSONObject content) { - try { - return content.toString(2); - } catch (JSONException e) { - // panic, don't format then - return content.toString(); - } - } - -} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java index aa6595d..abd6653 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java @@ -11,7 +11,7 @@ import android.widget.TextView; import android.widget.Toast; import com.trianguloy.urlchecker.R; -import com.trianguloy.urlchecker.dialogs.JsonEditor; +import com.trianguloy.urlchecker.activities.JsonEditorInterface; import com.trianguloy.urlchecker.utilities.generics.GenericPref; import com.trianguloy.urlchecker.utilities.methods.AndroidUtils; import com.trianguloy.urlchecker.utilities.methods.HttpUtils; @@ -31,7 +31,7 @@ import java.util.List; /** * Manages the local catalog with the rules */ -public class ClearUrlCatalog { +public class ClearUrlCatalog implements JsonEditorInterface { /* ------------------- constants ------------------- */ @@ -182,16 +182,34 @@ public class ClearUrlCatalog { * Show the rules editor dialog */ public void showEditor() { - JsonEditor.show(getCatalog(), getBuiltIn(), R.string.mClear_editor, cntx, content -> { - if (setRules(content, false) != Result.ERROR) { - // saved data, close dialog - return true; - } else { - // invalid data, keep dialog and show why - Toast.makeText(cntx, R.string.toast_invalid, Toast.LENGTH_LONG).show(); - return false; - } - }); + showEditor(cntx); + } + + @Override + public JSONObject getJson() { + return getCatalog(); + } + + @Override + public JSONObject getBuiltInJson() { + return getBuiltIn(); + } + + @Override + public String saveJson(JSONObject data) { + if (setRules(data, false) != Result.ERROR) { + // saved data, close dialog + return null; + } else { + // invalid data, keep dialog and show why + Toast.makeText(cntx, R.string.toast_invalid, Toast.LENGTH_LONG).show(); + return "Invalid"; + } + } + + @Override + public int getEditorDescription() { + return R.string.mClear_editor; } /** diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Hosts.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Hosts.java index 705311c..5bc286f 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Hosts.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Hosts.java @@ -9,7 +9,6 @@ import com.trianguloy.urlchecker.R; import com.trianguloy.urlchecker.utilities.generics.JsonCatalog; import com.trianguloy.urlchecker.utilities.methods.HttpUtils; import com.trianguloy.urlchecker.utilities.methods.JavaUtils; -import com.trianguloy.urlchecker.utilities.methods.StreamUtils; import com.trianguloy.urlchecker.utilities.wrappers.InternalFile; import com.trianguloy.urlchecker.utilities.wrappers.ProgressDialog; @@ -55,7 +54,7 @@ public class Hosts { ); if (showEditor) builder - .setNeutralButton(R.string.json_edit, (dialog, which) -> data.showEditor()); + .setNeutralButton(R.string.json_editor, (dialog, which) -> data.showEditor()); builder.show(); } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/PatternCatalog.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/PatternCatalog.java index 2846d63..540dbf4 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/PatternCatalog.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/PatternCatalog.java @@ -1,6 +1,5 @@ package com.trianguloy.urlchecker.modules.companions; -import android.app.Activity; import android.content.Context; import com.trianguloy.urlchecker.R; @@ -15,7 +14,7 @@ import org.json.JSONObject; */ public class PatternCatalog extends JsonCatalog { - public PatternCatalog(Activity cntx) { + public PatternCatalog(Context cntx) { super(cntx, "patterns", R.string.mPttrn_editor); } diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/generics/JsonCatalog.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/generics/JsonCatalog.java index ec4d40d..a37a3f4 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/generics/JsonCatalog.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/generics/JsonCatalog.java @@ -1,9 +1,8 @@ package com.trianguloy.urlchecker.utilities.generics; -import android.app.Activity; import android.content.Context; -import com.trianguloy.urlchecker.dialogs.JsonEditor; +import com.trianguloy.urlchecker.activities.JsonEditorInterface; import com.trianguloy.urlchecker.utilities.wrappers.InternalFile; import org.json.JSONException; @@ -12,13 +11,13 @@ import org.json.JSONObject; /** * Represents a generic catalog */ -public abstract class JsonCatalog { +public abstract class JsonCatalog implements JsonEditorInterface { - private final Activity cntx; + private final Context cntx; private final InternalFile custom; private final int editorDescription; - public JsonCatalog(Activity cntx, String fileName, int editorDescription) { + public JsonCatalog(Context cntx, String fileName, int editorDescription) { this.cntx = cntx; this.editorDescription = editorDescription; custom = new InternalFile(fileName, cntx); @@ -71,11 +70,30 @@ public abstract class JsonCatalog { return custom.set(content.toString()); } - /** - * Shows a dialog to manually edit the catalog - */ + + @Override + public JSONObject getJson() { + return getCatalog(); + } + + @Override + public JSONObject getBuiltInJson() { + return getBuiltIn(); + } + + @Override + public String saveJson(JSONObject data) { + return save(data) ? null : "Unable to save"; + } + + @Override + public int getEditorDescription() { + return editorDescription; + } + + public void showEditor() { - JsonEditor.show(getCatalog(), getBuiltIn(), editorDescription, cntx, this::save); + showEditor(cntx); } } \ No newline at end of file diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java index 41c0cd2..820f50c 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java @@ -14,9 +14,7 @@ import java.util.List; */ public interface JavaUtils { - /** - * Converts an iterator to a list - */ + /** Converts an iterator to a list */ static List toList(Iterator iterator) { List list = new ArrayList<>(); while (iterator.hasNext()) { @@ -25,9 +23,7 @@ public interface JavaUtils { return list; } - /** - * Converts a string into a json object, returns empty on failure - */ + /** Converts a string into a json object, returns empty on failure */ static JSONObject toJson(String content) { try { return new JSONObject(content); @@ -37,9 +33,7 @@ public interface JavaUtils { } } - /** - * Clamps a value between two other values. - */ + /** Clamps a value between two other values. */ static int clamp(int min, int value, int max) { return min <= max ? Math.max(min, Math.min(value, max)) // just in case @@ -108,17 +102,13 @@ public interface JavaUtils { else list.add(element); } - /** - * java.util.function.Consumer requires api 24 - */ + /** java.util.function.Consumer requires api 24 */ @FunctionalInterface interface Consumer { void accept(T t); } - /** - * java.util.function.Function requires api 24 - */ + /** java.util.function.Function requires api 24 */ @FunctionalInterface interface Function { R apply(T t); @@ -129,9 +119,7 @@ public interface JavaUtils { return t -> !function.apply(t); } - /** - * java.util.function.UnaryOperator requires api 24 - */ + /** java.util.function.UnaryOperator requires api 24 */ @FunctionalInterface interface UnaryOperator extends Function { } diff --git a/app/src/main/res/drawable/format.xml b/app/src/main/res/drawable/format.xml index 7edc1b4..d10bbc2 100644 --- a/app/src/main/res/drawable/format.xml +++ b/app/src/main/res/drawable/format.xml @@ -5,6 +5,6 @@ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/layout/activity_json_editor.xml b/app/src/main/res/layout/activity_json_editor.xml new file mode 100644 index 0000000..557455c --- /dev/null +++ b/app/src/main/res/layout/activity_json_editor.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/config_clearurls.xml b/app/src/main/res/layout/config_clearurls.xml index cc1e914..7e9381f 100644 --- a/app/src/main/res/layout/config_clearurls.xml +++ b/app/src/main/res/layout/config_clearurls.xml @@ -53,7 +53,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:text="@string/json_edit" /> + android:text="@string/json_editor" /> \ No newline at end of file diff --git a/app/src/main/res/layout/config_hosts.xml b/app/src/main/res/layout/config_hosts.xml index de08e1f..46fdf40 100644 --- a/app/src/main/res/layout/config_hosts.xml +++ b/app/src/main/res/layout/config_hosts.xml @@ -14,13 +14,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> -