package de.bsd.zwitscher.preferences;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.*;
import de.bsd.zwitscher.R;
/**
* A preference that allows to add/remove values. Items are stored in the key
* separated by the passed separator; default separator is comma (',').
*
* @attr separator A separator to separate the items in the preferences entry.
* @attr hint A hint that is displayed in the edit text box. This is a reference to a string resource.
* @attr askBeforeDelete Should an alert be shown before deleting an entry?
*
* @author Heiko W. Rupp
*/
public class ExpandableListPreference extends DialogPreference implements TextView.OnEditorActionListener,
AdapterView.OnItemLongClickListener{
private static final String EXPANDABLE_LIST_PREFERENCE = "ExpandableListPreference";
EditText inputField;
List<String> items = new ArrayList<String>();
ListView listView;
ArrayAdapter<String> arrayAdapter;
String key ;
String separator;
boolean askBeforeDelete = true;
private static final String DEFAULT_SEPARATOR = ",";
String hint;
private VerifyCallback verifierCallback;
public ExpandableListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
String separatorAttribute = attrs.getAttributeValue(null,"separator");
if (separatorAttribute==null)
separator = DEFAULT_SEPARATOR;
else
separator = separatorAttribute;
askBeforeDelete = attrs.getAttributeBooleanValue(null,"askBeforeDelete",true);
int hint1 = attrs.getAttributeResourceValue(null, "hint", 0);
if (hint1!=0)
hint = context.getString(hint1);
setPersistent(false); // we are persisting
setDialogLayoutResource(R.layout.expandable_list_preference);
key = getKey();
String verifierCallbackName = attrs.getAttributeValue(null, "verifierCallback");
verifierCallback = loadCallbackClass(verifierCallbackName);
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view); // Passed view is the expandable_list_preference layout.
inputField = (EditText) view.findViewById(R.id.elp_input);
inputField.setOnEditorActionListener(this);
if (hint!=null)
inputField.setHint(hint);
listView = (ListView) view.findViewById(R.id.elp_list);
// get persisted values and fill into items
SharedPreferences sp = getPreferenceManager().getSharedPreferences();
if (sp.contains(key)) {
String val = sp.getString(key,"");
items.clear();
items.addAll(Arrays.asList(val.split(separator))); // TODO escaping?
}
arrayAdapter = new ArrayAdapter<String>(getContext(),R.layout.expandable_list_item,items);
listView.setAdapter(arrayAdapter);
listView.setOnItemLongClickListener(this);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
// User has clicked ok, but not return - take the contents of the input
// field.
String s = inputField.getText().toString();
if (!items.contains(s) && !s.equals("") && !s.equals("\n")) {// For whatever reason we are called twice.
boolean valid = true;
// If a verifier is defined, invoke it
if (verifierCallback!=null) {
valid = verifierCallback.verify(s);
}
if (valid) {
items.add(s);
}
else {
String tmp = getContext().getString(R.string.invalid_list_item,s);
Toast.makeText(getContext(),tmp,Toast.LENGTH_LONG).show();
}
}
if (items.size()>0) {
SharedPreferences sp = getPreferenceManager().getSharedPreferences();
SharedPreferences.Editor editor = sp.edit();
StringBuilder sb = new StringBuilder();
Iterator<String> iter = items.iterator();
while (iter.hasNext()) {
sb.append(iter.next());
if (iter.hasNext())
sb.append(separator);
}
editor.putString(key,sb.toString());
editor.commit();
}
}
/**
* Called when the dialog is about to be displayed
* @param v The view this is invoked on
* @param actionId The action that happened
* @param event Even that has happened
* @return true if we consumed the event. False otherwise.
*/
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if(actionId == EditorInfo.IME_NULL || actionId== EditorInfo.IME_ACTION_SEND){
String s = inputField.getText().toString();
if (!items.contains(s) && !s.equals("") && !s.equals("\n")) // For whatever reason we are called twice.
items.add(s);
inputField.setText("");
// refresh list
arrayAdapter.notifyDataSetChanged();
return true;
}
return false;
}
/**
* Called on a long click on an item. Meant to delete the selected item.
* If the global option askBeforeDelete is set, a Dialog will be shown.
* @param parent AdapterView
* @param view Clicked view
* @param position Position of the selected item
* @param id id of the item
* @return true if the event has been consumed.
*/
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
if (askBeforeDelete) {
Context context = getContext();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(context.getString(R.string.wantToRemove,items.get(position)))
.setCancelable(false);
builder.setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
items.remove(position);
arrayAdapter.notifyDataSetChanged();
}
});
builder.setNegativeButton(context.getString(R.string.no), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
else {
items.remove(position);
arrayAdapter.notifyDataSetChanged();
}
return true;
}
private VerifyCallback loadCallbackClass(String verifierCallbackName) {
try {
Class clazz = Class.forName(verifierCallbackName);
if (clazz==null) {
Log.e(EXPANDABLE_LIST_PREFERENCE,"Class " + verifierCallbackName + " not found");
return null;
}
Class[] ifs = clazz.getInterfaces();
if (ifs==null) {
Log.e(EXPANDABLE_LIST_PREFERENCE,"Class " + verifierCallbackName + " implements no interfaces");
return null;
}
boolean found = false;
for (Class c : ifs) {
if (c.getName().equals("de.bsd.zwitscher.preferences.VerifyCallback"))
found = true;
}
if (!found) {
Log.e(EXPANDABLE_LIST_PREFERENCE,"Class " + verifierCallbackName + " does not implement de.bsd.zwitscher.preferences.VerifyCallback");
return null;
}
VerifyCallback callback = (VerifyCallback) clazz.newInstance();
return callback;
} catch (Exception e) {
Log.e(EXPANDABLE_LIST_PREFERENCE,"Loading of " + verifierCallbackName + " failed: " + e.getLocalizedMessage());
e.printStackTrace();
return null;
}
}
}