package org.azavea.otm.ui; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.View; import android.widget.EditText; import android.widget.ListView; import com.google.common.base.Function; import org.azavea.otm.R; import org.azavea.otm.adapters.LinkedHashMapAdapter; import org.azavea.otm.data.Model; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import static org.azavea.otm.adapters.LinkedHashMapAdapter.Entry; public abstract class FilterableListDisplay<T extends Model> extends ListActivity { /** * @return The Resource id of the hint text to show in the filter textbox */ protected abstract int getFilterHintTextId(); /** * @return The key used to store the model data in the intent result */ protected abstract String getIntentDataKey(); @Override public void onCreate(Bundle data) { super.onCreate(data); setContentView(R.layout.filterable_list_selector); EditText filterEditText = (EditText) findViewById(R.id.filter_text); filterEditText.setHint(getFilterHintTextId()); } public void renderList(LinkedHashMapAdapter<T> adapter) { setListAdapter(adapter); setupFiltering(adapter); } private void setupFiltering(final LinkedHashMapAdapter<T> adapter) { EditText filterEditText = (EditText) findViewById(R.id.filter_text); setKeyboardChangeEvents(adapter, filterEditText); setTextWatcherEvents(adapter, filterEditText); } /** * Listen on events of the filter text box to pass along filter text * and invalidate the current view */ private void setTextWatcherEvents(final LinkedHashMapAdapter<T> adapter, EditText filterEditText) { filterEditText.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { adapter.getFilter().filter(s); adapter.notifyDataSetChanged(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } }); } /** * Listen on events the keyboard emits when finished editing * ('Done' and 'Next' are the ENTER event). * The Keyboard ENTER event on a ListActivity will re-render * the list, so the current view must be invalidated */ private void setKeyboardChangeEvents(final LinkedHashMapAdapter<T> adapter, EditText filterEditText) { filterEditText.setOnKeyListener((v, keyCode, event) -> { if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { adapter.notifyDataSetChanged(); } return false; }); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { //noinspection unchecked Model selection = ((Entry<Model>) l.getItemAtPosition(position)).value; Intent result = new Intent(); result.putExtra(getIntentDataKey(), selection.getData().toString()); setResult(RESULT_OK, result); finish(); } public LinkedHashMap<CharSequence, List<T>> groupListByKeyFirstLetter(T[] list, Function<T, String> getKey) { if (getKey == null) { throw new IllegalArgumentException("getKey cannot be null"); } //noinspection ConstantConditions Arrays.sort(list, (a, b) -> getKey.apply(a).compareToIgnoreCase(getKey.apply(b))); LinkedHashMap<CharSequence, List<T>> itemSections = new LinkedHashMap<>(26); for (char c = 'A'; c <= 'Z'; c++) { itemSections.put(Character.toString(c), new ArrayList<>()); } for (T item : list) { //noinspection ConstantConditions char firstChar = getKey.apply(item).toUpperCase().trim().charAt(0); if (firstChar < 'A' || firstChar > 'Z') { firstChar = 'A'; } String key = Character.toString(firstChar); List<T> section = itemSections.get(key); section.add(item); } return itemSections; } }