package com.markupartist.iglaset.activity; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.markupartist.android.widget.ActionBar; import com.markupartist.android.widget.ActionBar.IntentAction; import com.markupartist.iglaset.R; import com.markupartist.iglaset.provider.Tag; import com.markupartist.iglaset.provider.TagsStore; import com.markupartist.iglaset.util.ListUtils; import com.markupartist.iglaset.util.MultiHashMap; import com.markupartist.iglaset.util.StringUtils; import com.markupartist.iglaset.widget.SearchAction; import com.markupartist.iglaset.widget.SectionedAdapter; import android.app.Dialog; import android.app.ListActivity; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; import android.os.AsyncTask; import android.os.Bundle; import android.util.SparseBooleanArray; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; /** * @author marco * Activity responsible for displaying a drink category's attached tags and allowing the user to * do detailed tag searching. */ public class TagActivity extends ListActivity implements View.OnClickListener { static final String EXTRA_CATEGORY_ID = "com.markupartist.iglaset.search.categoryId"; static final String EXTRA_CATEGORY_NAME = "com.markupartist.iglaset.search.categoryName"; private static final int DIALOG_SEARCH_NETWORK_PROBLEM = 0; private SectionedAdapter sectionedAdapter; private MultiHashMap<String, Tag> tagMap; private GetTagsTask getTagsTask; private int categoryId; /** * @author marco * Data holder for passing data between screen rotations. */ private static class InstanceHolder { /** * Selected items on rotation. */ SparseBooleanArray selectedItems; /** * Available tags on rotation. */ MultiHashMap<String, Tag> tags; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle extras = getIntent().getExtras(); setContentView(R.layout.tag_list); ActionBar actionBar = (ActionBar) findViewById(R.id.actionbar); actionBar.setHomeAction(new IntentAction(this, StartActivity.createIntent(this), R.drawable.ic_actionbar_home_default)); actionBar.setTitle(extras.getString(EXTRA_CATEGORY_NAME)); actionBar.addAction(new SearchAction() { @Override public void performAction(View view) { onSearchRequested(); } }); categoryId = extras.getInt(EXTRA_CATEGORY_ID); View tagSearchLayout = this.findViewById(R.id.tagSearchLayout); tagSearchLayout.setOnClickListener(this); sectionedAdapter = new SectionedAdapter() { protected View getHeaderView(Section section, int index, View convertView, ViewGroup parent) { TextView textView = (TextView) convertView; if(null == textView) { textView = (TextView) getLayoutInflater().inflate(R.layout.header, null); } textView.setText(section.caption); return textView; } }; getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // Use old values if available final InstanceHolder holder = (InstanceHolder) getLastNonConfigurationInstance(); if(null != holder && null != holder.tags) { setTagMap(holder.tags); updateSelected(holder.selectedItems); updateSelectedTagText(); } else { launchGetTagsTask(categoryId); } } @Override protected void onDestroy() { cancelGetTagsTask(); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); } @Override public Object onRetainNonConfigurationInstance() { InstanceHolder holder = new InstanceHolder(); holder.tags = this.tagMap; holder.selectedItems = getListView().getCheckedItemPositions(); return holder; } @Override protected Dialog onCreateDialog(int id) { switch(id) { case DIALOG_SEARCH_NETWORK_PROBLEM: return DialogFactory.createNetworkProblemDialog( this, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch(which) { case Dialog.BUTTON_POSITIVE: launchGetTagsTask(categoryId); break; case Dialog.BUTTON_NEGATIVE: finish(); break; } } }); } return null; } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); updateSelectedTagText(); } @Override public void onClick(View v) { switch(v.getId()) { case R.id.tagSearchLayout: doSearch(); break; default: break; } } private void doSearch() { Intent searchIntent = new Intent(this, SearchResultActivity.class); searchIntent.putExtra(SearchResultActivity.EXTRA_SEARCH_CATEGORY_ID, categoryId); // Transfer the IDs of the checked items to an array SparseBooleanArray checkedItems = getListView().getCheckedItemPositions(); ArrayList<Integer> checkedIds = new ArrayList<Integer>(); for(int i=0; i<checkedItems.size(); ++i) { if(true == checkedItems.valueAt(i)) { Tag tag = (Tag) getListView().getItemAtPosition(checkedItems.keyAt(i)); checkedIds.add(tag.getId()); } } TextView numSelectedView = (TextView) findViewById(R.id.tagSearchSelectedText); searchIntent.putExtra(SearchResultActivity.EXTRA_SEARCH_TAGS, checkedIds); searchIntent.putExtra(SearchResultActivity.EXTRA_SEARCH_TAGS_SELECTED, numSelectedView.getText()); startActivity(searchIntent); } /** * Update ListView selection. This is necessary since the ListView will retain the selected * items on screen rotation but there's no way of fetching those items from the program. * As a result you will see items checked but a call to getListView().getCheckedItemPositions * will return an empty list. * @param selected Checkbox state before rotation. */ private void updateSelected(SparseBooleanArray selected) { for(int i=0; i<selected.size(); ++i) { getListView().setItemChecked( selected.keyAt(i), selected.valueAt(i)); } } /** * Update the text showing the selected tags (if any). */ private void updateSelectedTagText() { TextView numSelectedView = (TextView) findViewById(R.id.tagSearchSelectedText); StringBuilder builder = new StringBuilder(); SparseBooleanArray selected = getListView().getCheckedItemPositions(); if(0 == selected.size()) { builder.append(getText(R.string.no_selected_tags)); } else { // Concatenate the name of the selected tags. ArrayList<String> selectedList = new ArrayList<String>(); for(int i=0; i<selected.size(); ++i) { if(true == selected.valueAt(i)) { Tag tag = (Tag) getListView().getItemAtPosition(selected.keyAt(i)); selectedList.add(tag.getName()); } } builder.append(Integer.toString(selectedList.size())).append(" "); builder.append(selectedList.size() == 1 ? getText(R.string.tag) : getText(R.string.tags)).append(": "); builder.append(StringUtils.join(selectedList.toArray(), ", ")); } numSelectedView.setText(builder.toString()); } private void setTagMap(MultiHashMap<String, Tag> tagMap) { LinearLayout progressBar = (LinearLayout) findViewById(R.id.search_progress); progressBar.setVisibility(View.GONE); this.tagMap = tagMap; if(tagMap.size() == 0) { // Instantly search if we have no tag categories doSearch(); finish(); } else { populateList(this.tagMap); } } private void populateList(MultiHashMap<String, Tag> tagMap) { int sectionId = 1; for(Map.Entry<String, List<Tag>> entry : tagMap.entrySet()) { ArrayAdapter<Tag> adapter = new ArrayAdapter<Tag>(this, android.R.layout.simple_list_item_multiple_choice, entry.getValue()) { @Override public long getItemId(int position) { return getItem(position).getId(); } }; sectionedAdapter.addSection(sectionId++, entry.getKey(), adapter); } // notifyDataSetChanged causes null references in the adapter's // findViewById for some reason. setListAdapter(sectionedAdapter); } /** * Launch an async task to fetch tags assigned to a specified category. If a tag fetch task is * already running then that task will get canceled. * @param category Category tags to fetch. */ private void launchGetTagsTask(int category) { cancelGetTagsTask(); getTagsTask = new GetTagsTask(); getTagsTask.execute(category); } /** * Cancel the getTagsTask if one is running. If the task has not been created or if it's * not executing then this will do nothing. */ private void cancelGetTagsTask() { if(null != getTagsTask && getTagsTask.getStatus() == AsyncTask.Status.RUNNING) { getTagsTask.cancel(true); getTagsTask = null; } } private class GetTagsTask extends AsyncTask<Integer, Void, MultiHashMap<String, Tag>> { @Override protected MultiHashMap<String, Tag> doInBackground(Integer... params) { MultiHashMap<String, Tag> tagMap = null; try { ArrayList<Tag> categoryTags = TagsStore.getTags(params[0]); tagMap = ListUtils.toMultiHashMap(categoryTags); } catch (IOException e) { e.printStackTrace(); } return tagMap; } @Override public void onProgressUpdate(Void... values) { setProgressBarIndeterminateVisibility(true); } @Override protected void onPostExecute(MultiHashMap<String, Tag> result) { setProgressBarIndeterminateVisibility(false); if(null != result) { setTagMap(result); } else { showDialog(DIALOG_SEARCH_NETWORK_PROBLEM); } } } }