content: package io.virtualapp.widgets;
  
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.RectF;
  import android.os.Handler;
  import android.support.annotation.Nullable;
  import android.support.v7.widget.RecyclerView;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.MotionEvent;
  import android.view.View;
  
  import io.virtualapp.R;
  
  /**
   * @author Aidan Follestad (afollestad)
   */
  public class DragSelectRecyclerView extends RecyclerView {
  
      private static final boolean LOGGING = false;
      private static final int AUTO_SCROLL_DELAY = 25;
      private int mLastDraggedIndex = -1;
      private DragSelectRecyclerViewAdapter<?> mAdapter;
      private int mInitialSelection;
      private boolean mDragSelectActive;
      private int mMinReached;
      private int mMaxReached;
      private int mHotspotHeight;
      private int mHotspotOffsetTop;
      private int mHotspotOffsetBottom;
      private int mHotspotTopBoundStart;
      private int mHotspotTopBoundEnd;
      private int mHotspotBottomBoundStart;
      private int mHotspotBottomBoundEnd;
      private int mAutoScrollVelocity;
      private FingerListener mFingerListener;
      private boolean mInTopHotspot;
      private boolean mInBottomHotspot;
      private Handler mAutoScrollHandler;
      private Runnable mAutoScrollRunnable = new Runnable() {
          @Override
          public void run() {
              if (mAutoScrollHandler == null)
                  return;
              if (mInTopHotspot) {
                  scrollBy(0, -mAutoScrollVelocity);
                  mAutoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
              } else if (mInBottomHotspot) {
                  scrollBy(0, mAutoScrollVelocity);
                  mAutoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
              }
          }
      };
      private RectF mTopBoundRect;
      private RectF mBottomBoundRect;
      private Paint mDebugPaint;
      private boolean mDebugEnabled = false;
  
      public DragSelectRecyclerView(Context context) {
          super(context);
          init(context, null);
      }
  
      public DragSelectRecyclerView(Context context, AttributeSet attrs) {
          super(context, attrs);
          init(context, attrs);
      }
  
      public DragSelectRecyclerView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          init(context, attrs);
      }
  
      private static void LOG(String message, Object... args) {
          //noinspection PointlessBooleanExpression
          if (!LOGGING) return;
          if (args != null) {
              Log.d("DragSelectRecyclerView", String.format(message, args));
          } else {
              Log.d("DragSelectRecyclerView", message);
          }
      }
  
      private void init(Context context, AttributeSet attrs) {
          mAutoScrollHandler = new Handler();
          final int defaultHotspotHeight = context.getResources().getDimensionPixelSize(R.dimen.dsrv_defaultHotspotHeight);
  
          if (attrs != null) {
              TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DragSelectRecyclerView, 0, 0);
              try {
                  boolean autoScrollEnabled = a.getBoolean(R.styleable.DragSelectRecyclerView_dsrv_autoScrollEnabled, true);
                  if (!autoScrollEnabled) {
                      mHotspotHeight = -1;
                      mHotspotOffsetTop = -1;
                      mHotspotOffsetBottom = -1;
                      LOG("Auto-scroll disabled");
                  } else {
                      mHotspotHeight = a.getDimensionPixelSize(
                              R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspotHeight, defaultHotspotHeight);
                      mHotspotOffsetTop = a.getDimensionPixelSize(
                              R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetTop, 0);
                      mHotspotOffsetBottom = a.getDimensionPixelSize(
                              R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetBottom, 0);
                      LOG("Hotspot height = %d", mHotspotHeight);
                  }
              } finally {
                  a.recycle();
              }
          } else {
              mHotspotHeight = defaultHotspotHeight;
              LOG("Hotspot height = %d", mHotspotHeight);
          }
      }
  
      public void setFingerListener(@Nullable FingerListener listener) {
          this.mFingerListener = listener;
      }
  
      @Override
      protected void onMeasure(int widthSpec, int heightSpec) {
          super.onMeasure(widthSpec, heightSpec);
          if (mHotspotHeight > -1) {
              mHotspotTopBoundStart = mHotspotOffsetTop;
              mHotspotTopBoundEnd = mHotspotOffsetTop + mHotspotHeight;
              mHotspotBottomBoundStart = (getMeasuredHeight() - mHotspotHeight) - mHotspotOffsetBottom;
              mHotspotBottomBoundEnd = getMeasuredHeight() - mHotspotOffsetBottom;
              LOG("RecyclerView height = %d", getMeasuredHeight());
              LOG("Hotspot top bound = %d to %d", mHotspotTopBoundStart, mHotspotTopBoundStart);
              LOG("Hotspot bottom bound = %d to %d", mHotspotBottomBoundStart, mHotspotBottomBoundEnd);
          }
      }
  
      public boolean setDragSelectActive(boolean active, int initialSelection) {
          if (active && mDragSelectActive) {
              LOG("Drag selection is already active.");
              return false;
          }
          mLastDraggedIndex = -1;
          mMinReached = -1;
          mMaxReached = -1;
          if (!mAdapter.isIndexSelectable(initialSelection)) {
              mDragSelectActive = false;
              mInitialSelection = -1;
              mLastDraggedIndex = -1;
              LOG("Index %d is not selectable.", initialSelection);
              return false;
          }
          mAdapter.setSelected(initialSelection, true);
          mDragSelectActive = active;
          mInitialSelection = initialSelection;
          mLastDraggedIndex = initialSelection;
          if (mFingerListener != null)
              mFingerListener.onDragSelectFingerAction(true);
          LOG("Drag selection initialized, starting at index %d.", initialSelection);
          return true;
      }
  
      /**
       * Use {@link #setAdapter(DragSelectRecyclerViewAdapter)} instead.
       */
      @Override
      @Deprecated
      public void setAdapter(Adapter adapter) {
          if (!(adapter instanceof DragSelectRecyclerViewAdapter<?>))
              throw new IllegalArgumentException("Adapter must be a DragSelectRecyclerViewAdapter.");
          setAdapter((DragSelectRecyclerViewAdapter<?>) adapter);
      }
  
      public void setAdapter(DragSelectRecyclerViewAdapter<?> adapter) {
          super.setAdapter(adapter);
          mAdapter = adapter;
      }
  
      private int getItemPosition(MotionEvent e) {
          final View v = findChildViewUnder(e.getX(), e.getY());
          if (v == null) return NO_POSITION;
          if (v.getTag() == null || !(v.getTag() instanceof ViewHolder))
              throw new IllegalStateException("Make sure your adapter makes a call to super.onBindViewHolder(), and doesn't override itemView tags.");
          final ViewHolder holder = (ViewHolder) v.getTag();
          return holder.getAdapterPosition();
      }
  
      public final void enableDebug() {
          mDebugEnabled = true;
          invalidate();
      }
  
      @Override
      public void onDraw(Canvas c) {
          super.onDraw(c);
  
          if (mDebugEnabled) {
              if (mDebugPaint == null) {
                  mDebugPaint = new Paint();
                  mDebugPaint.setColor(Color.BLACK);
                  mDebugPaint.setAntiAlias(true);
                  mDebugPaint.setStyle(Paint.Style.FILL);
                  mTopBoundRect = new RectF(0, mHotspotTopBoundStart, getMeasuredWidth(), mHotspotTopBoundEnd);
                  mBottomBoundRect = new RectF(0, mHotspotBottomBoundStart, getMeasuredWidth(), mHotspotBottomBoundEnd);
              }
              c.drawRect(mTopBoundRect, mDebugPaint);
              c.drawRect(mBottomBoundRect, mDebugPaint);
          }
      }
  
      @Override
      public boolean dispatchTouchEvent(MotionEvent e) {
          if (mAdapter.getItemCount() == 0)
              return super.dispatchTouchEvent(e);
  
          if (mDragSelectActive) {
              if (e.getAction() == MotionEvent.ACTION_UP) {
                  mDragSelectActive = false;
                  mInTopHotspot = false;
                  mInBottomHotspot = false;
                  mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
                  if (mFingerListener != null)
                      mFingerListener.onDragSelectFingerAction(false);
                  return true;
              } else if (e.getAction() == MotionEvent.ACTION_MOVE) {
                  // Check for auto-scroll hotspot
                  if (mHotspotHeight > -1) {
                      if (e.getY() >= mHotspotTopBoundStart && e.getY() <= mHotspotTopBoundEnd) {
                          mInBottomHotspot = false;
                          if (!mInTopHotspot) {
                              mInTopHotspot = true;
                              LOG("Now in TOP hotspot");
                              mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
                              mAutoScrollHandler.postDelayed(mAutoScrollRunnable, AUTO_SCROLL_DELAY);
                          }
  
                          final float simulatedFactor = mHotspotTopBoundEnd - mHotspotTopBoundStart;
                          final float simulatedY = e.getY() - mHotspotTopBoundStart;
                          mAutoScrollVelocity = (int) (simulatedFactor - simulatedY) / 2;
  
                          LOG("Auto scroll velocity = %d", mAutoScrollVelocity);
                      } else if (e.getY() >= mHotspotBottomBoundStart && e.getY() <= mHotspotBottomBoundEnd) {
                          mInTopHotspot = false;
                          if (!mInBottomHotspot) {
                              mInBottomHotspot = true;
                              LOG("Now in BOTTOM hotspot");
                              mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
                              mAutoScrollHandler.postDelayed(mAutoScrollRunnable, AUTO_SCROLL_DELAY);
                          }
  
                          final float simulatedY = e.getY() + mHotspotBottomBoundEnd;
                          final float simulatedFactor = mHotspotBottomBoundStart + mHotspotBottomBoundEnd;
                          mAutoScrollVelocity = (int) (simulatedY - simulatedFactor) / 2;
  
                          LOG("Auto scroll velocity = %d", mAutoScrollVelocity);
                      } else if (mInTopHotspot || mInBottomHotspot) {
                          LOG("Left the hotspot");
                          mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
                          mInTopHotspot = false;
                          mInBottomHotspot = false;
                      }
                  }
  
                  // Drag selection logic
                  // NOTE: DISABLE IT
  //                if (itemPosition != NO_POSITION && mLastDraggedIndex != itemPosition) {
  //                    mLastDraggedIndex = itemPosition;
  //                    if (mMinReached == -1) mMinReached = mLastDraggedIndex;
  //                    if (mMaxReached == -1) mMaxReached = mLastDraggedIndex;
  //                    if (mLastDraggedIndex > mMaxReached)
  //                        mMaxReached = mLastDraggedIndex;
  //                    if (mLastDraggedIndex < mMinReached)
  //                        mMinReached = mLastDraggedIndex;
  //                    if (mAdapter != null)
  //                        mAdapter.selectRange(mInitialSelection, mLastDraggedIndex, mMinReached, mMaxReached);
  //                    if (mInitialSelection == mLastDraggedIndex) {
  //                        mMinReached = mLastDraggedIndex;
  //                        mMaxReached = mLastDraggedIndex;
  //                    }
  //                }
                  return true;
              }
          }
          return super.dispatchTouchEvent(e);
      }
  
      public interface FingerListener {
          void onDragSelectFingerAction(boolean fingerDown);
      }
  }