package com.markupartist.iglaset.activity;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.markupartist.android.widget.ActionBar;
import com.markupartist.android.widget.ActionBar.IntentAction;
import com.markupartist.iglaset.R;
import com.markupartist.iglaset.activity.SearchDrinksTask.SearchDrinkCompletedListener;
import com.markupartist.iglaset.activity.SearchDrinksTask.SearchDrinkErrorListener;
import com.markupartist.iglaset.activity.SearchDrinksTask.SearchDrinkProgressUpdatedListener;
import com.markupartist.iglaset.provider.AuthStore;
import com.markupartist.iglaset.provider.AuthenticationException;
import com.markupartist.iglaset.provider.Drink;
import com.markupartist.iglaset.provider.RatingSearchCriteria;
import com.markupartist.iglaset.provider.RecommendationSearchCriteria;
import com.markupartist.iglaset.provider.SearchCriteria;
import com.markupartist.iglaset.provider.TextSearchCriteria;
import com.markupartist.iglaset.widget.SearchAction;
import com.markupartist.iglaset.IglasetApplication;
public class SearchResultActivity extends ListActivity implements
SearchDrinkCompletedListener, SearchDrinkProgressUpdatedListener, SearchDrinkErrorListener {
/**
* Class used to store data between orientation switches.
*/
private static class ConfigurationData {
ArrayList<Drink> drinks;
SearchCriteria searchCriteria;
}
/**
* Log tag.
*/
static final String TAG = SearchResultActivity.class.getSimpleName();
/**
* Action for triggering a search for user recommendations.
*/
static final String ACTION_USER_RECOMMENDATIONS =
"com.markupartist.iglaset.action.USER_RECOMMENDATIONS";
/**
* Actions for triggering a search for rated articles.
*/
static final String ACTION_USER_RATINGS =
"com.markupartist.iglaset.action.USER_RATINGS";
static final String EXTRA_SEARCH_BARCODE =
"com.markupartist.iglaset.search.barcode";
static final String EXTRA_SEARCH_CATEGORY_ID =
"com.markupartist.iglaset.search.categoryId";
static final String EXTRA_SEARCH_TAGS =
"com.markupartist.iglaset.search.tags";
static final String EXTRA_SEARCH_TAGS_SELECTED =
"com.markupartist.iglaset.search.tagsString";
static final String EXTRA_CLICKED_DRINK =
"com.markupartist.iglaset.search.clickedDrink";
static final int DIALOG_SEARCH_NETWORK_PROBLEM = 0;
static final int DIALOG_DRINK_IMAGE = 1;
static final int DIALOG_SELECT_SORTING = 2;
private DrinkAdapter mListAdapter;
private ArrayList<Drink> mDrinks;
private AuthStore.Authentication mAuthentication;
private SearchCriteria mSearchCriteria;
/**
* Common click listener used for all the images in the list. Note that
* each image will have to come with its corresponding drink attached
* as setTag. This will then be extracted in mImageClickListener.onClick.
*/
private View.OnClickListener mImageClickListener;
/**
* Placeholder for the drink clicked on in the list. This will be set
* in mImageClickListener's onClick event and will later be used to
* create the drink image viewer dialog.
*/
private Drink mClickedDrink;
/**
* Task object to be executed when searching.
*/
private SearchDrinksTask mSearchDrinksTask;
/**
* Footer progress.
*/
private View mFooterProgressView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.search_result);
ActionBar actionBar = (ActionBar) findViewById(R.id.actionbar);
actionBar.setTitle(R.string.search_results);
actionBar.setHomeAction(new IntentAction(this, StartActivity.createIntent(this), R.drawable.ic_actionbar_home_default));
actionBar.addAction(new SearchAction() {
@Override
public void performAction(View view) {
onSearchRequested();
}
});
try {
mAuthentication = AuthStore.getInstance().getAuthentication(this);
} catch (AuthenticationException e) {
Log.e(TAG, "User not authenticated...");
mAuthentication = null;
}
mImageClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickedDrink = (Drink) v.getTag();
showDialog(DIALOG_DRINK_IMAGE);
}
};
// Check if already have some data, used if screen is rotated.
ConfigurationData data = (ConfigurationData) getLastNonConfigurationInstance();
if(data != null) {
mDrinks = data.drinks;
mSearchCriteria = data.searchCriteria;
}
if(mDrinks != null) {
onDrinkData(mDrinks);
} else if(mSearchCriteria != null) {
setProgressBarIndeterminateVisibility(true);
createSearchDrinksTask().execute(mSearchCriteria);
} else {
final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
// Search actions
if (Intent.ACTION_SEARCH.equals(queryAction)) {
final String queryString = queryIntent.getStringExtra(SearchManager.QUERY);
// Record the query string in the recent queries suggestions provider.
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE);
suggestions.saveRecentQuery(queryString, null);
final TextView searchText = (TextView) findViewById(R.id.search_progress_text);
String searchingText = searchText.getText() + " \"" + queryString + "\"";
searchText.setText(searchingText);
actionBar.setTitle(searchingText);
mSearchCriteria = new TextSearchCriteria();
mSearchCriteria.setQuery(queryString);
} else if (ACTION_USER_RECOMMENDATIONS.equals(queryAction)) {
actionBar.setTitle(R.string.recommendations_label);
mSearchCriteria = new RecommendationSearchCriteria();
((RecommendationSearchCriteria) mSearchCriteria).setUserId(
mAuthentication.v2.userId);
} else if (ACTION_USER_RATINGS.equals(queryAction)) {
actionBar.setTitle(R.string.rated_articles_label);
mSearchCriteria = new RatingSearchCriteria();
((RatingSearchCriteria) mSearchCriteria).setUserId(mAuthentication.v2.userId);
} else {
Log.d(TAG, "Unknown search criteria. Falling back to empty text.");
mSearchCriteria = new TextSearchCriteria();
}
// Search parameters
if (queryIntent.hasExtra(EXTRA_SEARCH_CATEGORY_ID)) {
final int category = queryIntent.getExtras()
.getInt(EXTRA_SEARCH_CATEGORY_ID);
actionBar.setTitle(R.string.search_results);
mSearchCriteria.setCategory(category);
}
if (queryIntent.hasExtra(EXTRA_SEARCH_BARCODE)) {
String barcode = queryIntent.getStringExtra(EXTRA_SEARCH_BARCODE);
actionBar.setTitle(R.string.search_results);
mSearchCriteria.setBarcode(barcode);
}
if (queryIntent.hasExtra(EXTRA_SEARCH_TAGS)) {
actionBar.setTitle(R.string.search_results);
ArrayList<Integer> tags = queryIntent.getIntegerArrayListExtra(EXTRA_SEARCH_TAGS);
TextView selectedTags = (TextView) findViewById(R.id.tagSearchSelectedText);
selectedTags.setText(queryIntent.getStringExtra(EXTRA_SEARCH_TAGS_SELECTED));
selectedTags.setVisibility(View.VISIBLE);
mSearchCriteria.setTags(tags);
}
if(mSearchCriteria.supportsSorting()) {
int mode = getApp().getSearchSortMode(
mSearchCriteria.getClass(),
mSearchCriteria.getDefaultSortMode());
mSearchCriteria.setSortMode(mode);
}
mSearchCriteria.setAuthentication(mAuthentication);
setProgressBarIndeterminateVisibility(true);
createSearchDrinksTask().execute(mSearchCriteria);
}
this.registerReceiver(mBroadcastReceiver, new IntentFilter(Intents.ACTION_PUBLISH_DRINK));
}
private IglasetApplication getApp() {
return (IglasetApplication) getApplication();
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Publish an updated drink in the UI.
if(intent.getAction().equals(Intents.ACTION_PUBLISH_DRINK)) {
Bundle extras = intent.getExtras();
if(extras != null && extras.containsKey(Intents.EXTRA_DRINK)) {
final Drink drink = (Drink) intent.getExtras().get(Intents.EXTRA_DRINK);
onUpdatedDrink(drink);
} else {
Log.e(TAG, "No drink data available");
}
}
}
};
private SearchDrinksTask createSearchDrinksTask() {
if(null != mSearchDrinksTask && mSearchDrinksTask.getStatus() == AsyncTask.Status.RUNNING) {
mSearchDrinksTask.cancel(true);
}
mSearchDrinksTask = new SearchDrinksTask();
mSearchDrinksTask.setSearchDrinkCompletedListener(this);
mSearchDrinksTask.setSearchDrinkProgressUpdatedListener(this);
mSearchDrinksTask.setSearchDrinkErrorListener(this);
return mSearchDrinksTask;
}
public void onUpdatedDrink(Drink updatedDrink) {
// The incoming drink is most likely pointing to one of the drinks in
// our list but we can't be sure of that. Brute force find it.
for(Drink drink : mDrinks) {
if(drink.getId() == updatedDrink.getId()) {
drink.setUserRating(updatedDrink.getUserRating());
drink.setRatingCount(updatedDrink.getRatingCount());
drink.setCommentCount(updatedDrink.getCommentCount());
DrinkAdapter adapter = (DrinkAdapter) getListAdapter();
adapter.notifyDataSetChanged();
break;
}
}
}
/**
* Called before this activity is destroyed, returns the previous search
* result. This list is used if the screen is rotated. Then we don't need
* to search for it again.
*/
@Override
public Object onRetainNonConfigurationInstance() {
ConfigurationData data = new ConfigurationData();
data.drinks = mDrinks;
data.searchCriteria = mSearchCriteria;
return data;
}
@Override
protected void onResume() {
super.onResume();
// TODO: See if we can get the previous search criteria from here.
//Log.d(TAG, "onResume");
}
@Override
protected void onDestroy() {
if(null != mSearchDrinksTask && mSearchDrinksTask.getStatus() == AsyncTask.Status.RUNNING) {
mSearchDrinksTask.cancel(true);
}
unregisterReceiver(mBroadcastReceiver);
super.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(null != mClickedDrink) {
outState.putParcelable(EXTRA_CLICKED_DRINK, mClickedDrink);
}
//Log.d(TAG, "onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mClickedDrink = savedInstanceState.getParcelable(EXTRA_CLICKED_DRINK);
}
private void initList(ArrayList<Drink> drinks) {
mDrinks = drinks;
mFooterProgressView = getLayoutInflater().inflate(R.layout.loading_row, null);
getListView().addFooterView(mFooterProgressView, null, false);
mListAdapter = new DrinkAdapter(this, drinks);
setListAdapter(mListAdapter);
if (drinks.isEmpty()) {
View emptyLayout = findViewById(R.id.search_empty_layout);
emptyLayout.setVisibility(View.VISIBLE);
// Show a more verbose message if the user was browsing
// the recommendations
if(mSearchCriteria instanceof RecommendationSearchCriteria) {
TextView emptyText = (TextView) findViewById(R.id.search_empty);
emptyText.setText(R.string.no_recommendations_result);
}
if(mSearchCriteria.hasBarcode()) {
// Store the current orphan barcode so others can use it if necessary.
((IglasetApplication) getApplication()).storeOrphanBarcode(mSearchCriteria.getBarcode());
}
Button searchButton = (Button) findViewById(R.id.btn_search);
searchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSearchRequested();
}
});
}
if (!TextUtils.isEmpty(mSearchCriteria.getQuery())) {
setTitle(getText(R.string.search_results)
+ " \"" + mSearchCriteria.getQuery() + "\"");
}
LinearLayout progressBar = (LinearLayout) findViewById(R.id.search_progress);
progressBar.setVisibility(View.GONE);
getListView().removeFooterView(mFooterProgressView);
}
private void displayDrinkDetails(Drink drink) {
Intent i = new Intent(this, DrinkDetailActivity.class);
i.putExtra(Intents.EXTRA_DRINK, drink);
startActivity(i);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
displayDrinkDetails(mDrinks.get(position));
}
@Override
public boolean onSearchRequested() {
startSearch(null, false, null, false);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu_search_result, menu);
if(mSearchCriteria.supportsSorting() == false) {
menu.removeItem(R.id.menu_sort);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_sort:
showDialog(DIALOG_SELECT_SORTING);
return true;
case R.id.menu_search:
onSearchRequested();
return true;
case R.id.menu_home:
startActivity(new Intent(this, StartActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
public void onSearchDrinkComplete(ArrayList<Drink> result) {
setProgressBarIndeterminateVisibility(false);
if (mFooterProgressView != null) {
getListView().removeFooterView(mFooterProgressView);
}
onDrinkData(result);
}
private void onDrinkData(ArrayList<Drink> drinks) {
if(drinks.size() == 1) {
// Open the drink details and close the activity
displayDrinkDetails(drinks.get(0));
finish();
} else {
initList(drinks);
}
}
public void onSearchDrinkProgress() {
setProgressBarIndeterminateVisibility(true);
}
@Override
public void onSearchDrinkError(Exception exception) {
setProgressBarIndeterminateVisibility(false);
showDialog(DIALOG_SEARCH_NETWORK_PROBLEM);
}
@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:
setProgressBarIndeterminateVisibility(true);
createSearchDrinksTask().execute(mSearchCriteria);
break;
case Dialog.BUTTON_NEGATIVE:
finish();
break;
default:
break;
}
}
});
case DIALOG_DRINK_IMAGE:
return new DrinkImageViewerDialog(this, mClickedDrink);
case DIALOG_SELECT_SORTING:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Sortering");
builder.setSingleChoiceItems(
mSearchCriteria.getSortModeNames(this),
mSearchCriteria.getSortIndexFromMode(mSearchCriteria.getSortMode()),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
int sortMode = mSearchCriteria.getSortModeFromIndex(item);
mSearchCriteria.setSortMode(sortMode);
mSearchCriteria.setPage(1);
getApp().storeSearchSortMode(mSearchCriteria.getClass(), item);
setProgressBarIndeterminateVisibility(true);
createSearchDrinksTask().execute(mSearchCriteria);
removeDialog(DIALOG_SELECT_SORTING);
}
});
return builder.create();
default:
return null;
}
}
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case DIALOG_DRINK_IMAGE:
DrinkImageViewerDialog imageDialog = (DrinkImageViewerDialog) dialog;
imageDialog.setDrink(mClickedDrink);
imageDialog.load();
break;
default:
break;
}
}
/**
* List adapter for drinks. Deals with pagination internally.
*/
class DrinkAdapter extends ArrayAdapter<Drink> implements SearchDrinkCompletedListener {
private AtomicBoolean mShouldAppend = new AtomicBoolean(true);
private AtomicInteger mPage = new AtomicInteger(1);
private int mMaxResult = 100;
public DrinkAdapter(Context context, List<Drink> objects) {
super(context, R.layout.drink_detail, objects);
}
private boolean shouldAppend(int position) {
return (position == super.getCount() - 1
&& mShouldAppend.get()
&& super.getCount() >= 10
&& super.getCount() <= mMaxResult);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
DrinkViewHolder dvh = null;
if (shouldAppend(position)) {
getListView().addFooterView(mFooterProgressView);
getListView().forceLayout();
mSearchCriteria.setPage(mPage.addAndGet(1));
SearchDrinksTask task = createSearchDrinksTask();
task.setSearchDrinkCompletedListener(this);
setProgressBarIndeterminateVisibility(true);
task.execute(mSearchCriteria);
}
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.drink_detail, null);
dvh = new DrinkViewHolder(convertView);
convertView.setTag(dvh);
} else {
dvh = (DrinkViewHolder) convertView.getTag();
// Change to the default image or else the thumbnail will show
// an old image used in the reused view. The user won't notice it.
dvh.getImageView().setImageResource(R.drawable.noimage);
}
final Drink drink = getItem(position);
if (drink != null && dvh != null) {
dvh.populate(getContext(), drink, mImageClickListener);
if(drink.hasUserRating()) {
dvh.getRateView().setRating(drink.getUserRating());
} else {
float rating = (drink.hasEstimatedRating() ? drink.getEstimatedRating() : drink.getAverageRating());
dvh.getRateView().setRating(rating);
}
}
return convertView;
}
/**
* Append drinks to the list.
* @param drinks drinks to add to the list
* @return if we should append more or not
*/
protected boolean append(ArrayList<Drink> drinks) {
if (drinks.isEmpty()) {
mShouldAppend.set(false);
return mShouldAppend.get();
}
for (Drink drink : drinks) {
add(drink);
}
return mShouldAppend.get();
}
public void onSearchDrinkComplete(ArrayList<Drink> result) {
append(result);
setProgressBarIndeterminateVisibility(false);
if (mFooterProgressView != null) {
getListView().removeFooterView(mFooterProgressView);
}
}
public void onItemClick() {
Log.d("onItemClick", "");
}
}
}