2023-07-29 18:31:26 +02:00
|
|
|
package juloo.keyboard2;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.preference.Preference;
|
|
|
|
import android.preference.PreferenceGroup;
|
|
|
|
import android.util.AttributeSet;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
|
|
|
|
/** A list of preferences where the users can add items to the end and modify
|
|
|
|
and remove items. Backed by a string list. Implement user selection in
|
|
|
|
[select()]. */
|
2023-08-08 17:58:27 +02:00
|
|
|
public abstract class ListGroupPreference<E> extends PreferenceGroup
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
boolean _attached = false;
|
2023-08-08 17:58:27 +02:00
|
|
|
List<E> _values;
|
2023-07-30 19:48:54 +02:00
|
|
|
/** The "add" button currently displayed. */
|
|
|
|
AddButton _add_button = null;
|
2023-07-29 18:31:26 +02:00
|
|
|
|
|
|
|
public ListGroupPreference(Context context, AttributeSet attrs)
|
|
|
|
{
|
|
|
|
super(context, attrs);
|
|
|
|
setOrderingAsAdded(true);
|
|
|
|
setLayoutResource(R.layout.pref_listgroup_group);
|
2023-08-08 17:58:27 +02:00
|
|
|
_values = new ArrayList<E>();
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Overrideable */
|
|
|
|
|
|
|
|
/** The label to display on the item for a given value. */
|
2023-08-08 17:58:27 +02:00
|
|
|
abstract String label_of_value(E value, int i);
|
2023-07-29 18:31:26 +02:00
|
|
|
|
2023-07-30 18:01:25 +02:00
|
|
|
/** Called every time the list changes and allows to change the "Add" button
|
|
|
|
appearance.
|
|
|
|
[prev_btn] is the previously attached button, might be null. */
|
|
|
|
AddButton on_attach_add_button(AddButton prev_btn)
|
|
|
|
{
|
|
|
|
if (prev_btn == null)
|
|
|
|
return new AddButton(getContext());
|
|
|
|
return prev_btn;
|
|
|
|
}
|
|
|
|
|
2023-07-30 19:48:54 +02:00
|
|
|
/** Called every time the list changes and allows to disable the "Remove"
|
|
|
|
buttons on every items. Might be used to enforce a minimum number of
|
|
|
|
items. */
|
2023-08-10 20:55:42 +02:00
|
|
|
boolean should_allow_remove_item(E _value)
|
2023-07-30 19:48:54 +02:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-10 20:48:24 +02:00
|
|
|
/** Called when an item is added or modified. */
|
2023-08-08 17:58:27 +02:00
|
|
|
abstract void select(SelectionCallback<E> callback);
|
|
|
|
|
2023-08-10 20:48:24 +02:00
|
|
|
/** Called when an item is modified. */
|
|
|
|
void select(SelectionCallback<E> callback, E _old_value)
|
|
|
|
{
|
|
|
|
select(callback);
|
|
|
|
}
|
|
|
|
|
2023-08-08 17:58:27 +02:00
|
|
|
/** A separate class is used as the same serializer must be used in the
|
|
|
|
static context. See [Serializer] below. */
|
|
|
|
abstract Serializer<E> get_serializer();
|
2023-07-29 18:31:26 +02:00
|
|
|
|
|
|
|
/** Load/save utils */
|
|
|
|
|
|
|
|
/** Read a value saved by preference from a [SharedPreferences] object.
|
2023-08-08 17:58:27 +02:00
|
|
|
[serializer] must be the same that is returned by [get_serializer()].
|
2023-07-29 18:31:26 +02:00
|
|
|
Returns [null] on error. */
|
2023-08-08 17:58:27 +02:00
|
|
|
static <E> List<E> load_from_preferences(String key,
|
|
|
|
SharedPreferences prefs, List<E> def, Serializer<E> serializer)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
String s = prefs.getString(key, null);
|
2023-08-08 17:58:27 +02:00
|
|
|
return (s != null) ? load_from_string(s, serializer) : def;
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 20:23:33 +02:00
|
|
|
/** Save items into the preferences. Does not call [prefs.commit()]. */
|
|
|
|
static <E> void save_to_preferences(String key, SharedPreferences.Editor prefs, List<E> items, Serializer<E> serializer)
|
|
|
|
{
|
|
|
|
prefs.putString(key, save_to_string(items, serializer));
|
|
|
|
}
|
|
|
|
|
2023-07-29 18:31:26 +02:00
|
|
|
/** Decode a list of string previously encoded with [save_to_string]. Returns
|
|
|
|
[null] on error. */
|
2023-08-08 17:58:27 +02:00
|
|
|
static <E> List<E> load_from_string(String inp, Serializer<E> serializer)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
List<E> l = new ArrayList<E>();
|
2023-07-29 18:31:26 +02:00
|
|
|
JSONArray arr = new JSONArray(inp);
|
|
|
|
for (int i = 0; i < arr.length(); i++)
|
2023-08-08 17:58:27 +02:00
|
|
|
l.add(serializer.load_item(arr.get(i)));
|
2023-07-29 18:31:26 +02:00
|
|
|
return l;
|
|
|
|
}
|
|
|
|
catch (JSONException e)
|
|
|
|
{
|
2023-08-15 20:23:33 +02:00
|
|
|
Logs.exn("load_from_string", e);
|
2023-07-29 18:31:26 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Encode a list of string so it can be passed to
|
|
|
|
[Preference.persistString()]. Decode with [load_from_string]. */
|
2023-08-08 17:58:27 +02:00
|
|
|
static <E> String save_to_string(List<E> items, Serializer<E> serializer)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
List<Object> serialized_items = new ArrayList<Object>();
|
|
|
|
for (E it : items)
|
2023-08-10 12:49:57 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
serialized_items.add(serializer.save_item(it));
|
|
|
|
}
|
2023-08-15 20:23:33 +02:00
|
|
|
catch (JSONException e)
|
|
|
|
{
|
|
|
|
Logs.exn("save_to_string", e);
|
|
|
|
}
|
2023-08-10 12:49:57 +02:00
|
|
|
}
|
2023-08-08 17:58:27 +02:00
|
|
|
return (new JSONArray(serialized_items)).toString();
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Protected API */
|
|
|
|
|
|
|
|
/** Set the values. If [persist] is [true], persist into the store. */
|
2023-08-08 17:58:27 +02:00
|
|
|
void set_values(List<E> vs, boolean persist)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
_values = vs;
|
|
|
|
reattach();
|
|
|
|
if (persist)
|
2023-08-08 17:58:27 +02:00
|
|
|
persistString(save_to_string(vs, get_serializer()));
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
2023-08-08 17:58:27 +02:00
|
|
|
void add_item(E v)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
_values.add(v);
|
|
|
|
set_values(_values, true);
|
|
|
|
}
|
|
|
|
|
2023-08-08 17:58:27 +02:00
|
|
|
void change_item(int i, E v)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
2023-07-30 23:34:48 +02:00
|
|
|
_values.set(i, v);
|
|
|
|
set_values(_values, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove_item(int i)
|
|
|
|
{
|
|
|
|
_values.remove(i);
|
2023-07-29 18:31:26 +02:00
|
|
|
set_values(_values, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Internal */
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
|
|
|
|
{
|
|
|
|
String input = (restoreValue) ? getPersistedString(null) : (String)defaultValue;
|
|
|
|
if (input != null)
|
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
List<E> values = load_from_string(input, get_serializer());
|
2023-07-29 18:31:26 +02:00
|
|
|
if (values != null)
|
|
|
|
set_values(values, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onAttachedToActivity()
|
|
|
|
{
|
|
|
|
super.onAttachedToActivity();
|
|
|
|
if (_attached)
|
|
|
|
return;
|
|
|
|
_attached = true;
|
|
|
|
reattach();
|
|
|
|
}
|
|
|
|
|
|
|
|
void reattach()
|
|
|
|
{
|
2023-07-30 18:01:25 +02:00
|
|
|
if (!_attached)
|
|
|
|
return;
|
2023-07-29 18:31:26 +02:00
|
|
|
removeAll();
|
|
|
|
int i = 0;
|
2023-08-08 17:58:27 +02:00
|
|
|
for (E v : _values)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
2023-08-10 20:55:42 +02:00
|
|
|
addPreference(this.new Item(getContext(), i, v));
|
2023-07-29 18:31:26 +02:00
|
|
|
i++;
|
|
|
|
}
|
2023-07-30 19:48:54 +02:00
|
|
|
_add_button = on_attach_add_button(_add_button);
|
|
|
|
_add_button.setOrder(Preference.DEFAULT_ORDER);
|
|
|
|
addPreference(_add_button);
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class Item extends Preference
|
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
final E _value;
|
2023-07-30 23:34:48 +02:00
|
|
|
final int _index;
|
2023-07-29 18:31:26 +02:00
|
|
|
|
2023-08-10 20:55:42 +02:00
|
|
|
public Item(Context ctx, int index, E value)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
super(ctx);
|
|
|
|
_value = value;
|
2023-07-30 23:34:48 +02:00
|
|
|
_index = index;
|
2023-07-29 18:31:26 +02:00
|
|
|
setPersistent(false);
|
2023-07-30 23:34:48 +02:00
|
|
|
setTitle(label_of_value(value, index));
|
2023-08-10 20:55:42 +02:00
|
|
|
if (should_allow_remove_item(value))
|
2023-07-30 19:48:54 +02:00
|
|
|
setWidgetLayoutResource(R.layout.pref_listgroup_item_widget);
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected View onCreateView(ViewGroup parent)
|
|
|
|
{
|
|
|
|
View v = super.onCreateView(parent);
|
2023-07-30 19:48:54 +02:00
|
|
|
View remove_btn = v.findViewById(R.id.pref_listgroup_remove_btn);
|
|
|
|
if (remove_btn != null)
|
|
|
|
remove_btn.setOnClickListener(new View.OnClickListener() {
|
2023-07-29 18:31:26 +02:00
|
|
|
@Override
|
|
|
|
public void onClick(View _v)
|
|
|
|
{
|
2023-07-30 23:34:48 +02:00
|
|
|
remove_item(_index);
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
});
|
2023-08-10 19:10:32 +02:00
|
|
|
v.setOnClickListener(new View.OnClickListener() {
|
|
|
|
@Override
|
|
|
|
public void onClick(View _v)
|
|
|
|
{
|
|
|
|
select(new SelectionCallback<E>() {
|
|
|
|
public void select(E value)
|
|
|
|
{
|
2023-08-10 20:55:42 +02:00
|
|
|
if (value == null)
|
|
|
|
remove_item(_index);
|
|
|
|
else
|
|
|
|
change_item(_index, value);
|
2023-08-10 19:10:32 +02:00
|
|
|
}
|
2023-08-10 20:55:42 +02:00
|
|
|
|
|
|
|
public boolean allow_remove() { return true; }
|
|
|
|
}, _value);
|
2023-08-10 19:10:32 +02:00
|
|
|
}
|
|
|
|
});
|
2023-07-29 18:31:26 +02:00
|
|
|
return v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-30 18:01:25 +02:00
|
|
|
class AddButton extends Preference
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
public AddButton(Context ctx)
|
|
|
|
{
|
|
|
|
super(ctx);
|
|
|
|
setPersistent(false);
|
|
|
|
setLayoutResource(R.layout.pref_listgroup_add_btn);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onClick()
|
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
select(new SelectionCallback<E>() {
|
|
|
|
public void select(E value)
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
|
|
|
add_item(value);
|
|
|
|
}
|
2023-08-10 20:55:42 +02:00
|
|
|
|
|
|
|
public boolean allow_remove() { return false; }
|
2023-07-29 18:31:26 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-08 17:58:27 +02:00
|
|
|
public interface SelectionCallback<E>
|
|
|
|
{
|
|
|
|
public void select(E value);
|
2023-08-10 20:55:42 +02:00
|
|
|
|
|
|
|
/** If this method returns [true], [null] might be passed to [select] to
|
|
|
|
remove the item. */
|
|
|
|
public boolean allow_remove();
|
2023-08-08 17:58:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Methods for serializing and deserializing abstract items.
|
|
|
|
[StringSerializer] is an implementation. */
|
|
|
|
public interface Serializer<E>
|
|
|
|
{
|
|
|
|
/** [obj] is an object returned by [save_item()]. */
|
2023-08-10 12:49:57 +02:00
|
|
|
E load_item(Object obj) throws JSONException;
|
2023-08-08 17:58:27 +02:00
|
|
|
|
|
|
|
/** Serialize an item into JSON. Might return an object that can be inserted
|
|
|
|
in a [JSONArray]. */
|
2023-08-10 12:49:57 +02:00
|
|
|
Object save_item(E v) throws JSONException;
|
2023-08-08 17:58:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static class StringSerializer implements Serializer<String>
|
2023-07-29 18:31:26 +02:00
|
|
|
{
|
2023-08-08 17:58:27 +02:00
|
|
|
public String load_item(Object obj) { return (String)obj; }
|
|
|
|
public Object save_item(String v) { return v; }
|
2023-07-29 18:31:26 +02:00
|
|
|
}
|
|
|
|
}
|