package ca.josephroque.bowlingcompanion.adapter; import android.app.Activity; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; 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.Constants; import ca.josephroque.bowlingcompanion.R; import ca.josephroque.bowlingcompanion.theme.Theme; import ca.josephroque.bowlingcompanion.utilities.DisplayUtils; import ca.josephroque.bowlingcompanion.wrapper.Series; /** * Created by Joseph Roque on 15-03-17. Manages series and their associated games for a ListView. Offers a callback * interface {@link SeriesAdapter.SeriesEventHandler} to handle interaction events. */ public class SeriesAdapter extends RecyclerView.Adapter<SeriesAdapter.SeriesViewHolder> implements Theme.ChangeableTheme, View.OnClickListener { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "SeriesAdapter"; /** 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; /** Textual representations of match play results. */ private static final String[] MATCH_PLAY_INDICATORS = {"", "W", "L", "T"}; /** Activity which created the instance of this object. */ private Activity mActivity; /** Instance of handler for callback on user action. */ private SeriesEventHandler mEventHandler; /** The {@link RecyclerView} this adapter is attached to. */ private RecyclerView mRecyclerView; /** List of series which will be displayed. */ private final List<Series> mListSeries; /** Cached drawable for stats icon. */ private Drawable mStatsDrawable; /** Cached drawable for copy icon. */ private Drawable mCopyDrawable; /** The last color set as the stats drawable filter. */ private int mDrawableFilter; /** Indicates minimum score values which will be highlighted when displayed. */ private int mMinimumScoreToHighlight = Constants.DEFAULT_GAME_HIGHLIGHT; /** Indicates minimum series total values which will be highlighted when displayed. */ private int mMinimumSeriesToHighlight = Constants.DEFAULT_SERIES_HIGHLIGHT; /** Indicates if the user is currently selecting a series to duplicate. */ private boolean mDuplicatingSeries; /** * Subclass of RecyclerView.ViewHolder to manage view which will display text to the user. */ public static class SeriesViewHolder extends RecyclerView.ViewHolder { /** Displays date of the series. */ private TextView mTextViewDate; /** Each TextView displays a different score in the series. */ private TextView[] mArrayTextViewGames; /** Each TextView displays a different match play result in the series. */ private TextView[] mArrayTextViewMatchPlay; /** Displays an icon to view the stats of a series. */ private ImageView mImageViewStats; /** Displays the sum of all the scores in the series. */ private TextView mTextViewSeriesTotal; /** Displays text to confirm deletion of item. */ private TextView mTextViewDelete; /** Displays icon to confirm deletion of item. */ private ImageView mImageViewDelete; /** Displays text to undo deletion of item. */ private TextView mTextViewUndo; /** * 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 */ @SuppressWarnings("CheckStyle") public SeriesViewHolder(View itemLayoutView, int viewType) { super(itemLayoutView); switch (viewType) { case VIEWTYPE_ACTIVE: mTextViewDate = (TextView) itemLayoutView.findViewById(R.id.tv_series_date); mImageViewStats = (ImageView) itemLayoutView.findViewById(R.id.iv_view_series_stats); mTextViewSeriesTotal = (TextView) itemLayoutView.findViewById(R.id.tv_series_total); // Adds text views by id to array mArrayTextViewGames = new TextView[Constants.MAX_NUMBER_LEAGUE_GAMES]; mArrayTextViewGames[0] = (TextView) itemLayoutView.findViewById(R.id.tv_series_game_1); mArrayTextViewGames[1] = (TextView) itemLayoutView.findViewById(R.id.tv_series_game_2); mArrayTextViewGames[2] = (TextView) itemLayoutView.findViewById(R.id.tv_series_game_3); mArrayTextViewGames[3] = (TextView) itemLayoutView.findViewById(R.id.tv_series_game_4); mArrayTextViewGames[4] = (TextView) itemLayoutView.findViewById(R.id.tv_series_game_5); mArrayTextViewMatchPlay = new TextView[Constants.MAX_NUMBER_LEAGUE_GAMES]; mArrayTextViewMatchPlay[0] = (TextView) itemLayoutView.findViewById(R.id.tv_series_match_play_1); mArrayTextViewMatchPlay[1] = (TextView) itemLayoutView.findViewById(R.id.tv_series_match_play_2); mArrayTextViewMatchPlay[2] = (TextView) itemLayoutView.findViewById(R.id.tv_series_match_play_3); mArrayTextViewMatchPlay[3] = (TextView) itemLayoutView.findViewById(R.id.tv_series_match_play_4); mArrayTextViewMatchPlay[4] = (TextView) itemLayoutView.findViewById(R.id.tv_series_match_play_5); break; case VIEWTYPE_DELETED: mImageViewDelete = (ImageView) itemLayoutView.findViewById(R.id.iv_delete); mTextViewDelete = (TextView) itemLayoutView.findViewById(R.id.tv_delete); mTextViewUndo = (TextView) itemLayoutView.findViewById(R.id.tv_undo_delete); break; default: throw new IllegalArgumentException("invalid view type: " + viewType); } } } /** * Sets member variables to parameters. * * @param activity activity which created this instance * @param eventHandler handles on click/long click events on views * @param listSeries list of series to be displayed in RecyclerView */ public SeriesAdapter( Activity activity, SeriesEventHandler eventHandler, List<Series> listSeries) { this.mActivity = activity; this.mEventHandler = eventHandler; this.mListSeries = listSeries; } @Override public SeriesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView; switch (viewType) { case VIEWTYPE_ACTIVE: itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_series, parent, false); break; case VIEWTYPE_DELETED: itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_deleted, parent, false); break; default: throw new IllegalArgumentException("invalid view type: " + viewType); } return new SeriesViewHolder(itemView, viewType); } @Override public void onBindViewHolder(final SeriesViewHolder holder, final int position) { final int viewType = getItemViewType(position); switch (viewType) { case VIEWTYPE_ACTIVE: bindActiveSeriesItem(holder, position); break; case VIEWTYPE_DELETED: bindDeletedSeriesItem(holder, position); break; default: throw new IllegalArgumentException("Invalid view type: " + viewType); } } /** * Sets properties of a view holder for a deleted series item. * * @param holder view holder for deleted item * @param position position of deleted item */ private void bindDeletedSeriesItem(SeriesViewHolder holder, int position) { final String nameToDelete = mListSeries.get(position).getSeriesDate(); final long idToDelete = mListSeries.get(position).getSeriesId(); 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.onSItemUndoDelete(idToDelete); else mEventHandler.onSItemDelete(idToDelete); } } }; holder.itemView.setOnClickListener(null); holder.itemView.setBackgroundColor(Theme.getTertiaryThemeColor()); holder.mTextViewDelete.setText(String.format(Locale.CANADA, holder.mTextViewDelete.getResources() .getString(R.string.text_click_to_delete), nameToDelete)); holder.mTextViewDelete.setOnClickListener(onClickListener); holder.mTextViewUndo.setOnClickListener(onClickListener); holder.mImageViewDelete.setOnClickListener(onClickListener); } /** * Sets properties of a view holder for an active series item. * * @param holder view holder for active item * @param position position of active item */ private void bindActiveSeriesItem(final SeriesViewHolder holder, int position) { holder.mTextViewDate.setText(mListSeries.get(position).getSeriesDate()); int matchPlayPadding = (int) mRecyclerView.getResources().getDimension(R.dimen.match_play_text_padding); int matchPlayHalfPadding = (int) mRecyclerView.getResources().getDimension(R.dimen.match_play_text_padding_2); List<Short> games = mListSeries.get(position).getSeriesGames(); List<Byte> matchPlayResults = mListSeries.get(position).getSeriesMatchPlayResults(); boolean hasMatchPlayResults = PreferenceManager.getDefaultSharedPreferences(holder.itemView.getContext()) .getBoolean(Constants.KEY_SHOW_MATCH_RESULTS, true); if (hasMatchPlayResults) { hasMatchPlayResults = false; for (Byte b : matchPlayResults) { if (b != Constants.MATCH_PLAY_NONE) { hasMatchPlayResults = true; break; } } } short seriesTotal = mListSeries.get(position).getSeriesTotal(); setSeriesTotalText(holder.mTextViewSeriesTotal, seriesTotal); final int numberOfGamesInSeries = games.size(); for (int i = 0; i < numberOfGamesInSeries; i++) { // Highlights a score if it is over 300 or applies default theme if not short gameScore = games.get(i); setGameScoreText(holder.mArrayTextViewGames[i], gameScore); if (hasMatchPlayResults) { byte matchPlay = matchPlayResults.get(i); holder.mArrayTextViewGames[i].setPadding(matchPlayPadding, matchPlayPadding, matchPlayPadding, matchPlayHalfPadding); holder.mArrayTextViewMatchPlay[i].setVisibility(View.VISIBLE); holder.mArrayTextViewMatchPlay[i].setText(MATCH_PLAY_INDICATORS[matchPlay]); colorMatchPlayText(holder.mArrayTextViewMatchPlay[i], matchPlay); } else { holder.mArrayTextViewGames[i].setPadding(matchPlayPadding, matchPlayPadding, matchPlayPadding, matchPlayPadding); holder.mArrayTextViewMatchPlay[i].setVisibility(View.GONE); } } for (int i = numberOfGamesInSeries; i < Constants.MAX_NUMBER_LEAGUE_GAMES; i++) { holder.mArrayTextViewGames[i].setText(null); holder.mArrayTextViewMatchPlay[i].setText(null); holder.mArrayTextViewMatchPlay[i].setVisibility(View.GONE); } setItemDrawable(holder.mImageViewStats); holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { if (mEventHandler != null) { mEventHandler.onEditClick(mRecyclerView.getChildAdapterPosition(holder.itemView)); return true; } return false; } }); holder.mImageViewStats.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mEventHandler != null) { if (mDuplicatingSeries) mEventHandler.onDuplicateClick(mRecyclerView.getChildAdapterPosition(holder.itemView)); else mEventHandler.onViewStatsClick(mRecyclerView.getChildAdapterPosition(holder.itemView)); } } }); 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)); } holder.itemView.setOnClickListener(this); } /** * Sets the color of the textview based on the match play results. * * @param textViewMatchPlay text view to color * @param matchPlay match play results */ private void colorMatchPlayText(TextView textViewMatchPlay, byte matchPlay) { if (!PreferenceManager.getDefaultSharedPreferences(textViewMatchPlay.getContext()) .getBoolean(Constants.KEY_HIGHLIGHT_MATCH_RESULTS, true)) { matchPlay = Constants.MATCH_PLAY_NONE; } switch (matchPlay) { case Constants.MATCH_PLAY_WON: textViewMatchPlay.setTextColor(DisplayUtils.getColorResource(textViewMatchPlay.getResources(), R.color.theme_green_tertiary)); textViewMatchPlay.setAlpha(1f); break; case Constants.MATCH_PLAY_LOST: textViewMatchPlay.setTextColor(DisplayUtils.getColorResource(textViewMatchPlay.getResources(), R.color.theme_red_tertiary)); textViewMatchPlay.setAlpha(1f); break; default: textViewMatchPlay.setTextColor(DisplayUtils.COLOR_BLACK); textViewMatchPlay.setAlpha(DisplayUtils.BLACK_SECONDARY_TEXT_ALPHA); break; } } /** * Sets the text view text to the series total of the game and highlights it accordingly. * * @param textViewTotal text view to set and highlight * @param seriesTotal total of the series */ private void setSeriesTotalText(TextView textViewTotal, short seriesTotal) { textViewTotal.setText(String.format(Locale.CANADA, "%d", seriesTotal)); if (seriesTotal >= mMinimumSeriesToHighlight) { textViewTotal.setBackgroundColor(Theme.getStatusThemeColor()); textViewTotal.setTextColor(DisplayUtils.COLOR_WHITE); textViewTotal.setAlpha(1f); } else { textViewTotal.setBackgroundColor(0x00000000); textViewTotal.setTextColor(DisplayUtils.COLOR_BLACK); textViewTotal.setAlpha(DisplayUtils.BLACK_SECONDARY_TEXT_ALPHA); } } /** * Sets the text view text to the score of the game and highlights it accordingly. * * @param textViewGame text view to set and highlight * @param gameScore score of the game */ private void setGameScoreText(TextView textViewGame, short gameScore) { textViewGame.setText(String.format(Locale.CANADA, "%d", gameScore)); if (gameScore >= mMinimumScoreToHighlight) { textViewGame.setTextColor(Theme.getTertiaryThemeColor()); textViewGame.setAlpha(1f); } else { textViewGame.setTextColor(DisplayUtils.COLOR_BLACK); textViewGame.setAlpha(DisplayUtils.BLACK_SECONDARY_TEXT_ALPHA); } } @Override public void onClick(View v) { // Calls relevant event handler method if (mEventHandler != null && mRecyclerView != null) { // If the user is duplicating a series, then the item clicked is duplicated if (mDuplicatingSeries) mEventHandler.onDuplicateClick(mRecyclerView.getChildAdapterPosition(v)); else mEventHandler.onSItemClick(mRecyclerView.getChildAdapterPosition(v)); } } @Override public int getItemCount() { return mListSeries.size(); } @Override public int getItemViewType(int position) { return (mListSeries.get(position).wasDeleted()) ? VIEWTYPE_DELETED : VIEWTYPE_ACTIVE; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); mRecyclerView = recyclerView; } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { super.onDetachedFromRecyclerView(recyclerView); // Releasing references mActivity = null; mEventHandler = null; mRecyclerView = null; mCopyDrawable = null; mStatsDrawable = null; } @Override public void updateTheme() { if (mActivity != null) { mMinimumScoreToHighlight = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(mActivity) .getString(Constants.KEY_HIGHLIGHT_SCORE, "-1")); if (mMinimumScoreToHighlight == -1) mMinimumScoreToHighlight = Constants.DEFAULT_GAME_HIGHLIGHT; else mMinimumScoreToHighlight = mMinimumScoreToHighlight * Constants.HIGHLIGHT_INCREMENT; mMinimumSeriesToHighlight = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(mActivity) .getString(Constants.KEY_HIGHLIGHT_SERIES, "-1")); if (mMinimumSeriesToHighlight == -1) mMinimumSeriesToHighlight = Constants.DEFAULT_SERIES_HIGHLIGHT; else mMinimumSeriesToHighlight = mMinimumSeriesToHighlight * Constants.HIGHLIGHT_INCREMENT; } notifyDataSetChanged(); } /** * Checks if the drawables {@code mStatsDrawable} and {@code mCopyDrawable} have been loaded and loads them if not. * Then, sets the relevant drawable of the image view. * * @param imageView image view to set drawable of */ private void setItemDrawable(ImageView imageView) { if (mStatsDrawable == null) mStatsDrawable = DisplayUtils.getDrawable(imageView.getResources(), R.drawable.ic_poll_black_24dp); if (mCopyDrawable == null) mCopyDrawable = DisplayUtils.getDrawable(imageView.getResources(), R.drawable.ic_content_copy_white_24dp); if (mDrawableFilter != Theme.getSecondaryThemeColor()) { mDrawableFilter = Theme.getSecondaryThemeColor(); mCopyDrawable.setColorFilter(mDrawableFilter, PorterDuff.Mode.SRC_IN); } if (mDuplicatingSeries) imageView.setImageDrawable(mCopyDrawable); else imageView.setImageDrawable(mStatsDrawable); } /** * Updates the appearance of the items in the adapter, to indicate if the user is selecting a series to duplicate. * * @param duplicating {@code true} to show a "copy" icon, {@code false} to show the usual "view stats" icon */ public void setDuplicatingSeries(boolean duplicating) { if (mDuplicatingSeries == duplicating) return; mDuplicatingSeries = duplicating; notifyDataSetChanged(); } /** * Provides methods to implement functionality when items in the RecyclerView are interacted with. */ public interface SeriesEventHandler { /** * Called when an item in the RecyclerView is clicked. * * @param position position of the item in the list */ void onSItemClick(final int position); /** * Called when an item in the RecyclerView is confirmed by user for deletion. * * @param id id of the deleted item */ void onSItemDelete(long id); /** * Called when the user undoes a delete on an item in the RecyclerView. * * @param id id of the undeleted item */ void onSItemUndoDelete(long id); /** * Called when an item is long pressed. * * @param position position of the item in the list */ void onEditClick(final int position); /** * Called when the stats image view for an item in the RecyclerView is clicked. * * @param position position of the item in the list */ void onViewStatsClick(final int position); /** * Called when the duplicate image view for an item in the RecyclerView is clicked. * * @param position position of the item in the list */ void onDuplicateClick(final int position); } }