package ca.josephroque.bowlingcompanion.adapter;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.Locale;
import ca.josephroque.bowlingcompanion.R;
import ca.josephroque.bowlingcompanion.theme.Theme;
import ca.josephroque.bowlingcompanion.utilities.DisplayUtils;
import ca.josephroque.bowlingcompanion.wrapper.LeagueEvent;
import ca.josephroque.bowlingcompanion.wrapper.NameAverageId;
/**
* Created by Joseph Roque on 15-03-13. Manages names of bowlers or leagues/events and their associated averages for a
* ListView. Offers a callback interface {@link NameAverageAdapter.NameAverageEventHandler} to handle interaction
* events.
*
* @param <T> Object of type NameAverageId which is displayed by this adapter
*/
public class NameAverageAdapter<T extends NameAverageId>
extends RecyclerView.Adapter<NameAverageAdapter.NameAverageViewHolder>
implements View.OnClickListener,
View.OnLongClickListener {
/** Identifies output from this class in Logcat. */
@SuppressWarnings("unused")
private static final String TAG = "NameAverageAdapter";
/** Represents an item in the list which is active. */
private static final int VIEWTYPE_ACTIVE = 0;
/** Represents an item in the list which has been deleted. */
private static final int VIEWTYPE_DELETED = 1;
/** Indicates data represents bowlers. */
public static final byte DATA_BOWLERS = 0;
/** Indicates data represents leagues and events. */
public static final byte DATA_LEAGUES_EVENTS = 1;
/** Instance of handler for callback on user action. */
private NameAverageEventHandler mEventHandler;
/** The {@link RecyclerView} that this adapter is attached to. */
private RecyclerView mRecyclerView;
/** List of names and averages to be displayed by the adapter. */
private final List<T> mListNamesAndAverages;
/** Cached drawables to display as icons for items. */
private Drawable[] mItemDrawables;
/** Type of data being represented by this object. */
private final byte mDataType;
/** Indicates how averages will be formatted - as an integer or to a single decimal place. */
private boolean mAverageAsDecimal = false;
/**
* Subclass of RecyclerView.ViewHolder to manage view which will display an image, and text to the user.
*/
public static final class NameAverageViewHolder
extends RecyclerView.ViewHolder {
/** Displays an image representing the type of data in the row. */
private ImageView mImageViewType;
/** Displays the name of the data in the row. */
private TextView mTextViewName;
/** Displays the average of the data in the row. */
private TextView mTextViewAverage;
/**
* Calls super constructor and gets instances of ImageView and TextView objects for member variables from
* itemLayoutView.
*
* @param itemLayoutView layout view containing views to display data
* @param viewType type of view
*/
private NameAverageViewHolder(View itemLayoutView, int viewType) {
super(itemLayoutView);
switch (viewType) {
case VIEWTYPE_ACTIVE:
mImageViewType = (ImageView) itemLayoutView.findViewById(R.id.iv_name_average_type);
mTextViewName = (TextView) itemLayoutView.findViewById(R.id.tv_name_avg_name);
mTextViewAverage = (TextView) itemLayoutView.findViewById(
R.id.tv_name_average_average);
break;
case VIEWTYPE_DELETED:
mImageViewType = (ImageView) itemLayoutView.findViewById(R.id.iv_delete);
mTextViewName = (TextView) itemLayoutView.findViewById(R.id.tv_delete);
mTextViewAverage = (TextView) itemLayoutView.findViewById(R.id.tv_undo_delete);
break;
default:
throw new IllegalArgumentException("view type is invalid: " + viewType);
}
}
}
/**
* Sets member variables to parameters.
*
* @param handler handles on click/long click events on views
* @param listNameAverages list of names and averages to be displayed
* @param dataType type of data being managed by this object
*/
public NameAverageAdapter(NameAverageEventHandler handler,
List<T> listNameAverages,
byte dataType) {
mEventHandler = handler;
mListNamesAndAverages = listNameAverages;
mDataType = dataType;
switch (mDataType) {
case DATA_BOWLERS:
mItemDrawables = new Drawable[1];
break;
case DATA_LEAGUES_EVENTS:
mItemDrawables = new Drawable[2];
break;
default:
throw new IllegalArgumentException("invalid data type: " + mDataType);
}
}
@Override
public NameAverageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
switch (viewType) {
case VIEWTYPE_ACTIVE:
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_name_average, parent, false);
break;
case VIEWTYPE_DELETED:
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_deleted, parent, false);
break;
default:
throw new IllegalArgumentException("view type is invalid: " + viewType);
}
return new NameAverageViewHolder(itemView, viewType);
}
@SuppressWarnings("CheckStyle")
@Override
public void onBindViewHolder(final NameAverageViewHolder holder, final int position) {
final int viewType = getItemViewType(position);
switch (viewType) {
case VIEWTYPE_ACTIVE:
// Sets text/images depending on data type
switch (mDataType) {
case DATA_BOWLERS:
holder.mTextViewName.setText(mListNamesAndAverages.get(position).getName());
if (mItemDrawables[0] == null)
mItemDrawables[0]
= DisplayUtils.getDrawable(holder.itemView.getResources(),
R.drawable.ic_person_black_24dp);
holder.mImageViewType.setImageDrawable(mItemDrawables[0]);
break;
case DATA_LEAGUES_EVENTS:
holder.mTextViewName.setText(mListNamesAndAverages.get(position).getName());
if (mItemDrawables[0] == null || mItemDrawables[1] == null) {
mItemDrawables[0]
= DisplayUtils.getDrawable(holder.itemView.getResources(),
R.drawable.ic_l_black_24dp);
mItemDrawables[1]
= DisplayUtils.getDrawable(holder.itemView.getResources(),
R.drawable.ic_e_black_24dp);
}
LeagueEvent leagueEvent = (LeagueEvent) mListNamesAndAverages.get(position);
holder.mImageViewType.setImageDrawable(
!leagueEvent.isEvent()
? mItemDrawables[0]
: mItemDrawables[1]);
break;
default:
throw new IllegalStateException("invalid mDataType: " + mDataType);
}
String average = DisplayUtils.getFormattedAverage(
Math.abs(mListNamesAndAverages.get(position).getAverage()), mAverageAsDecimal);
holder.mTextViewAverage.setText(String.format(Locale.CANADA, holder.mTextViewAverage.getResources()
.getString(R.string.text_average_placeholder), average));
if (mListNamesAndAverages.get(position).getAverage() < 0) {
holder.mTextViewAverage.setTextColor(ContextCompat.getColor(holder.mTextViewAverage.getContext(),
R.color.invalid_average));
} else {
holder.mTextViewAverage.setTextColor(ContextCompat.getColor(holder.mTextViewAverage.getContext(),
android.R.color.black));
}
if (position % 2 == 1) {
holder.itemView.setBackgroundColor(DisplayUtils.getColorResource(holder.itemView.getResources(),
R.color.secondary_background_offset));
} else {
holder.itemView.setBackgroundColor(DisplayUtils.getColorResource(holder.itemView.getResources(),
R.color.secondary_background));
}
// Sets actions on click/touch events
holder.itemView.setOnClickListener(this);
holder.itemView.setOnLongClickListener(this);
break;
case VIEWTYPE_DELETED:
String nameToDelete = mListNamesAndAverages.get(position).getName();
final long idToDelete = mListNamesAndAverages.get(position).getId();
final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEventHandler != null) {
if (v.getId() == R.id.tv_undo_delete)
mEventHandler.onNAItemUndoDelete(idToDelete);
else
mEventHandler.onNAItemDelete(idToDelete);
}
}
};
holder.itemView.setOnClickListener(null);
holder.itemView.setOnLongClickListener(null);
holder.itemView.setBackgroundColor(Theme.getTertiaryThemeColor());
holder.mTextViewName.setText(String.format(Locale.CANADA, holder.mTextViewName.getResources()
.getString(R.string.text_click_to_delete), nameToDelete));
holder.mTextViewName.setOnClickListener(onClickListener);
holder.mTextViewAverage.setOnClickListener(onClickListener);
holder.mImageViewType.setOnClickListener(onClickListener);
break;
default:
throw new IllegalArgumentException("invalid view type: " + viewType);
}
}
@Override
public void onClick(View v) {
// Calls relevant event handler method
if (mEventHandler != null && mRecyclerView != null)
mEventHandler.onNAItemClick(mRecyclerView.getChildAdapterPosition(v));
}
@Override
public boolean onLongClick(View v) {
if (mEventHandler != null && mRecyclerView != null) {
mEventHandler.onNAItemLongClick(mRecyclerView.getChildAdapterPosition(v));
return true;
}
return false;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
// Releasing references
mRecyclerView = null;
mEventHandler = null;
mItemDrawables = null;
}
@Override
public int getItemCount() {
return mListNamesAndAverages.size();
}
@Override
public int getItemViewType(int position) {
return (mListNamesAndAverages.get(position).wasDeleted())
? VIEWTYPE_DELETED
: VIEWTYPE_ACTIVE;
}
/**
* Updates the adapter to format the averages to either one decimal place or none.
*
* @param averageAsDecimal {@code true} to show up to one decimal place, {@code false} otherwise
*/
public void setDisplayAverageAsDecimal(boolean averageAsDecimal) {
if (averageAsDecimal == mAverageAsDecimal)
// Nothing changed
return;
mAverageAsDecimal = averageAsDecimal;
notifyDataSetChanged();
}
/**
* Provides methods to implement functionality when items in the RecyclerView are interacted with.
*/
public interface NameAverageEventHandler {
/**
* Called when an item in the RecyclerView is clicked.
*
* @param position position of the item in the list
*/
void onNAItemClick(final int position);
/**
* Called when an item in the RecyclerView is long clicked.
*
* @param position position of the item in the list
*/
void onNAItemLongClick(final int position);
/**
* Called when an item in the RecyclerView is confirmed by user for deletion.
*
* @param id id of the deleted item
*/
void onNAItemDelete(long id);
/**
* Called when the user undoes a delete on an item in the RecyclerView.
*
* @param id id of the undeleted item
*/
void onNAItemUndoDelete(long id);
}
}