package com.gaiagps.iburn.adapters; import android.content.Context; import android.database.Cursor; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import com.tonicartos.superslim.LayoutManager; import com.tonicartos.superslim.LinearSLM; import java.util.List; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import timber.log.Timber; public abstract class SectionedCursorAdapter<T extends PlayaItemCursorAdapter.ViewHolder> extends PlayaItemCursorAdapter<T> { protected static final int VIEW_TYPE_HEADER = 0x01; protected static final int VIEW_TYPE_CONTENT = 0x00; List<Integer> headerPositions; public SectionedCursorAdapter(Context context, Cursor c, AdapterListener listener) { super(context, c, listener); initializeWithNewCursor(c); } @Override public void onBindViewHolder(T viewHolder, int position) { if (!mDataValid) { throw new IllegalStateException("this should only be called when the cursor is valid"); } if (isHeaderPosition(position)) { mCursor.moveToPosition(getCursorPositionForPosition(position+1)); // the next element informed this header onBindViewHolderHeader(viewHolder, mCursor, position); return; } else if (!mCursor.moveToPosition(getCursorPositionForPosition(position))) { Timber.e("couldn't move cursor to position " + position); // I've observed a crash report here when we threw an exception. Think it's preferable to // just not bind the view under the assumption that this is spurious behavior // caused by the recyclerview being fast scroller (Not sure this is possible?) before data bound. return; } onBindViewHolder(viewHolder, mCursor, position); } protected abstract void onBindViewHolder(T viewHolder, Cursor item, int position); protected abstract void onBindViewHolderHeader(T viewHolder, Cursor firstSectionItem, int position); /** * Convenience method for setting Linear SLM layout properties on a ViewHolder view. * This should be called for each call to {@link #onBindViewHolder(RecyclerView.ViewHolder, Cursor, int)} and * {@link #onBindViewHolderHeader(RecyclerView.ViewHolder, Cursor, int)} * * Use this method if you don't require anything other than standard linear section managers. * Override or set properties manually if you require other behavior */ protected void setLinearSlmParameters(T viewHolder, int position) { final LayoutManager.LayoutParams params = (LayoutManager.LayoutParams) viewHolder.itemView.getLayoutParams(); params.setSlm(LinearSLM.ID); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.setFirstPosition(getHeaderPositionForPosition(position)); viewHolder.itemView.setLayoutParams(params); } @Override public abstract String[] getRequiredProjection(); @Override public int getItemViewType(int position) { // TODO : Get header positions from query. count(s_date < +30m), count(s_date < +2hr) return isHeaderPosition(position) ? VIEW_TYPE_HEADER : VIEW_TYPE_CONTENT; } @Override public int getItemCount() { int superCount = super.getItemCount(); if (superCount != 0) return superCount + (headerPositions == null ? 0 : headerPositions.size()); return superCount; } @Override public long getItemId(int position) { if (isHeaderPosition(position)) { return getHeaderId(position); } return super.getItemId(getCursorPositionForPosition(position)); } public void changeCursor(Cursor cursor) { Observable.just(cursor) .subscribeOn(Schedulers.computation()) .map(this::initializeWithNewCursor) .observeOn(AndroidSchedulers.mainThread()) .subscribe(success -> { super.changeCursor(cursor); }); } public Cursor swapCursor(Cursor newCursor) { initializeWithNewCursor(newCursor); return super.swapCursor(newCursor); } boolean isHeaderPosition(int position) { return headerPositions != null && headerPositions.contains(position); } private long getHeaderId(int position) { // return something unlikely to conflict with database ids return Long.MAX_VALUE - headerPositions.indexOf(position); } private boolean initializeWithNewCursor(Cursor newCursor) { if (newCursor != null && newCursor.getCount() > 0) { _createHeadersForCursor(newCursor); } return true; } protected abstract List<Integer> createHeadersForCursor(Cursor cursor); private void _createHeadersForCursor(Cursor cursor) { cursor.moveToFirst(); headerPositions = createHeadersForCursor(cursor); cursor.moveToFirst(); } /** * @return the position of the header for the corresponding item position. * The value will be less than or equal to position. */ int getHeaderPositionForPosition(int position) { // TODO : Do a binary search? IF -1 return last header index? int headerIdx = getHeaderIndexForPosition(position); return headerIdx == -1 ? position : headerPositions.get(headerIdx); } /** * @return the index of the header for the current position, or -1 if none found */ int getHeaderIndexForPosition(int position) { // TODO : Do a binary search? for (int idx = headerPositions.size() - 1; idx >= 0; idx--) { if (headerPositions.get(idx) <= position) return idx; } return -1; } /** * @return the cursor position for the corresponding item position. Compensate for the presence of headers * e.g: Position 1 is cursor position 0, because position 0 is always the first header */ int getCursorPositionForPosition(int position) { return position - (getHeaderIndexForPosition(position) + 1); } }