package com.pluscubed.plustimer.ui.solvelist; import android.content.Context; import android.os.Bundle; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.pluscubed.plustimer.R; import com.pluscubed.plustimer.base.RecyclerViewUpdate; import com.pluscubed.plustimer.model.Solve; import com.pluscubed.plustimer.utils.PrefUtils; import com.pluscubed.plustimer.utils.Utils; import java.util.ArrayList; import java.util.List; /** * Acts as a MVP view (sort of...) */ public class SolveListAdapter extends RecyclerView.Adapter<SolveListAdapter.ViewHolder> implements SolveListAdapterView { private static final String STATE_SOLVES = "state_solves"; private static final String STATE_STATS = "state_stats"; private static final String STATE_INITIALIZED = "state_initialized"; private static final String STATE_BEST = "state_best"; private static final String STATE_WORST = "state_worst"; private static final int HEADER_VIEWTYPE = 2; private static final int HEADER_ID = -1; private final Context mContext; private Solve mBest; private Solve mWorst; private String mPuzzleTypeId; private List<Solve> mSolves; private String mStats; private boolean mSignEnabled; private boolean mMillisecondsEnabled; private SolveListPresenter mPresenter; private boolean mInitialized; public SolveListAdapter(Context context, Bundle savedInstanceState) { mContext = context; if (savedInstanceState != null) { mBest = savedInstanceState.getParcelable(STATE_BEST); mWorst = savedInstanceState.getParcelable(STATE_WORST); mSolves = savedInstanceState.getParcelableArrayList(STATE_SOLVES); mStats = savedInstanceState.getString(STATE_STATS); mInitialized = savedInstanceState.getBoolean(STATE_INITIALIZED); } else { mSolves = new ArrayList<>(); mStats = ""; mInitialized = false; } updateSignAndMillisecondsMode(); setHasStableIds(true); } @Override public boolean isInitialized() { return mInitialized; } public void setSolves(String puzzleTypeId, List<Solve> solves) { mPuzzleTypeId = puzzleTypeId; mSolves = solves; mInitialized = true; } public void onPresenterPrepared(SolveListPresenter presenter) { mPresenter = presenter; } public void onPresenterDestroyed() { mPresenter = null; } public void onSaveInstanceState(Bundle outState) { //TODO: TransactionTooLargeException is possible, but this is linked to the more serious problem of how much data is stored in memory in general outState.putParcelableArrayList(STATE_SOLVES, (ArrayList<Solve>) mSolves); outState.putString(STATE_STATS, mStats); outState.putParcelable(STATE_BEST, mBest); outState.putParcelable(STATE_WORST, mWorst); outState.putBoolean(STATE_INITIALIZED, mInitialized); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; if (viewType == HEADER_VIEWTYPE) { view = LayoutInflater.from(mContext).inflate(R.layout.list_item_solvelist_header, parent, false); } else { view = LayoutInflater.from(mContext).inflate(R.layout.list_item_solvelist, parent, false); } return new ViewHolder(view, viewType == HEADER_VIEWTYPE); } @Override public void onBindViewHolder(ViewHolder holder, int position) { if (position > 0) { position = position - 1; Solve s = mSolves.get(position); String timeString = s.getTimeString(mMillisecondsEnabled); if (s == mBest || s == mWorst) { holder.textView.setText(String.format("(%s)", timeString)); } else { holder.textView.setText(timeString); } String uiScramble = Utils.getUiScramble(mContext, s.getScramble(), mSignEnabled, mPuzzleTypeId).toBlocking().value(); holder.desc.setText(uiScramble); } else { holder.header.setText(mStats); } } @Override public int getItemViewType(int position) { return position == 0 ? HEADER_VIEWTYPE : 0; } @Override public int getItemCount() { return mSolves.size() + 1; } @Override public long getItemId(int position) { if (position == 0) return HEADER_ID; else return mSolves.get(position - 1).getId().hashCode(); } @Override public void notifyChange(RecyclerViewUpdate mode, Solve solve, String stats) { switch (mode) { case DATA_RESET: notifyDataSetChanged(); break; case INSERT: mSolves.add(0, solve); notifyDataSetChanged(); break; case REMOVE: mSolves.remove(solve); notifyDataSetChanged(); break; case SINGLE_CHANGE: for (int i = 0; i < mSolves.size(); i++) { Solve foundSolve = mSolves.get(i); if (foundSolve.getId().equals(solve.getId())) { mSolves.set(i, solve); notifyItemChanged(i + 1); break; } } break; case REMOVE_ALL: mSolves.clear(); notifyDataSetChanged(); break; } if (mode != RecyclerViewUpdate.REMOVE_ALL) { Solve oldBest = mBest; Solve oldWorst = mWorst; mBest = Utils.getBestSolveOfList(mSolves); mWorst = Utils.getWorstSolveOfList(mSolves); if (oldBest != null && !oldBest.equals(mBest)) { //indexOf old solve will only work for insert b/c it uses .equals of Solve, // but that's fine since in single change the old solve is updated already notifyItemChanged(mSolves.indexOf(oldBest) + 1); notifyItemChanged(mSolves.indexOf(mBest) + 1); } if (oldWorst != null && !oldWorst.equals(mWorst)) { notifyItemChanged(mSolves.indexOf(oldWorst) + 1); notifyItemChanged(mSolves.indexOf(mWorst) + 1); } } mStats = stats; notifyItemChanged(0); } @Override public void updateSignAndMillisecondsMode() { boolean signWasEnabled = mSignEnabled; boolean millisecondsWasEnabled = mMillisecondsEnabled; mSignEnabled = PrefUtils.isSignEnabled(mContext); mMillisecondsEnabled = PrefUtils.isDisplayMillisecondsEnabled(mContext); if (signWasEnabled != mSignEnabled || mMillisecondsEnabled != millisecondsWasEnabled) { notifyItemRangeChanged(1, mSolves.size()); } } public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public TextView desc; public TextView header; public ViewHolder(View v, boolean header) { super(v); if (header) { this.header = (TextView) v.findViewById(R.id.solvelist_header_stats_textview); } else { textView = (TextView) v.findViewById(R.id.list_item_solvelist_title_textview); desc = (TextView) v.findViewById(R.id.list_item_solvelist_desc_textview); v.setOnClickListener(view -> mPresenter.onSolveClicked(mSolves.get(getAdapterPosition() - 1))); } } } }