/*
* Copyright (C) 2015 Haruki Hasegawa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.h6ah4i.android.widget.advrecyclerview.draggable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.ViewGroup;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.utils.BaseWrapperAdapter;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
class DraggableItemWrapperAdapter<VH extends RecyclerView.ViewHolder> extends BaseWrapperAdapter<VH> implements SwipeableItemAdapter<VH> {
private static final String TAG = "ARVDraggableWrapper";
private static final int STATE_FLAG_INITIAL_VALUE = -1;
private static final boolean LOCAL_LOGV = false;
private static final boolean LOCAL_LOGD = false;
private static final boolean LOCAL_LOGI = true;
private static final boolean DEBUG_BYPASS_MOVE_OPERATION_MODE = false;
private RecyclerViewDragDropManager mDragDropManager;
private DraggableItemAdapter mDraggableItemAdapter;
private RecyclerView.ViewHolder mDraggingItem;
private ItemDraggableRange mDraggableRange;
private long mDraggingItemId = RecyclerView.NO_ID;
private int mDraggingItemInitialPosition = RecyclerView.NO_POSITION;
private int mDraggingItemCurrentPosition = RecyclerView.NO_POSITION;
public DraggableItemWrapperAdapter(RecyclerViewDragDropManager manager, RecyclerView.Adapter<VH> adapter) {
super(adapter);
mDraggableItemAdapter = getDraggableItemAdapter(adapter);
if (getDraggableItemAdapter(adapter) == null) {
throw new IllegalArgumentException("adapter does not implement DraggableItemAdapter");
}
if (manager == null) {
throw new IllegalArgumentException("manager cannot be null");
}
mDragDropManager = manager;
}
@Override
protected void onRelease() {
super.onRelease();
mDraggingItem = null;
mDraggableItemAdapter = null;
mDragDropManager = null;
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
final VH holder = super.onCreateViewHolder(parent, viewType);
if (holder instanceof DraggableItemViewHolder) {
((DraggableItemViewHolder) holder).setDragStateFlags(STATE_FLAG_INITIAL_VALUE);
}
return holder;
}
@Override
public void onBindViewHolder(VH holder, int position) {
if (isDragging()) {
final long itemId = holder.getItemId();
final int origPosition = convertToOriginalPosition(
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
if (itemId == mDraggingItemId && holder != mDraggingItem) {
if (mDraggingItem == null) {
if (LOCAL_LOGI) {
Log.i(TAG, "a new view holder object for the currently dragging item is assigned");
}
mDraggingItem = holder;
mDragDropManager.onNewDraggingItemViewBinded(holder);
} else {
Log.e(TAG, "an another view holder object for the currently dragging item is assigned");
}
}
int flags = RecyclerViewDragDropManager.STATE_FLAG_DRAGGING;
if (itemId == mDraggingItemId) {
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_ACTIVE;
}
if (mDraggableRange.checkInRange(position)) {
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_IN_RANGE;
}
safeUpdateFlags(holder, flags);
super.onBindViewHolder(holder, origPosition);
} else {
safeUpdateFlags(holder, 0);
super.onBindViewHolder(holder, position);
}
}
@Override
public long getItemId(int position) {
if (isDragging()) {
final int origPosition = convertToOriginalPosition(
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
return super.getItemId(origPosition);
} else {
return super.getItemId(position);
}
}
@Override
public int getItemViewType(int position) {
if (isDragging()) {
final int origPosition = convertToOriginalPosition(
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
return super.getItemViewType(origPosition);
} else {
return super.getItemViewType(position);
}
}
protected static int convertToOriginalPosition(int position, int dragInitial, int dragCurrent) {
if (dragInitial < 0 || dragCurrent < 0) {
// not dragging
return position;
} else {
if ((dragInitial == dragCurrent) ||
((position < dragInitial) && (position < dragCurrent)) ||
(position > dragInitial) && (position > dragCurrent)) {
return position;
} else if (dragCurrent < dragInitial) {
if (position == dragCurrent) {
return dragInitial;
} else {
return position - 1;
}
} else { // if (dragCurrent > dragInitial)
if (position == dragCurrent) {
return dragInitial;
} else {
return position + 1;
}
}
}
}
@Override
protected void onHandleWrappedAdapterChanged() {
if (shouldCancelDragOnDataUpdated()) {
cancelDrag();
} else {
super.onHandleWrappedAdapterChanged();
}
}
@Override
protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) {
if (shouldCancelDragOnDataUpdated()) {
cancelDrag();
} else {
super.onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount);
}
}
@Override
protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) {
if (shouldCancelDragOnDataUpdated()) {
cancelDrag();
} else {
super.onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount);
}
}
@Override
protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) {
if (shouldCancelDragOnDataUpdated()) {
cancelDrag();
} else {
super.onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount);
}
}
@Override
protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) {
if (shouldCancelDragOnDataUpdated()) {
cancelDrag();
} else {
super.onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount);
}
}
private boolean shouldCancelDragOnDataUpdated() {
//noinspection SimplifiableIfStatement
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
return false;
}
return isDragging();
}
private void cancelDrag() {
if (mDragDropManager != null) {
mDragDropManager.cancelDrag();
}
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/ void onDragItemStarted(RecyclerView.ViewHolder holder, ItemDraggableRange range) {
if (LOCAL_LOGD) {
Log.d(TAG, "onDragItemStarted(holder = " + holder + ")");
}
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
return;
}
if (holder.getItemId() == RecyclerView.NO_ID) {
throw new IllegalStateException("dragging target must provides valid ID");
}
mDraggingItemInitialPosition = holder.getAdapterPosition();
mDraggingItemCurrentPosition = mDraggingItemInitialPosition;
mDraggingItem = holder;
mDraggingItemId = holder.getItemId();
mDraggableRange = range;
notifyDataSetChanged();
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/ void onDragItemFinished(RecyclerView.ViewHolder holder, boolean result) {
if (LOCAL_LOGD) {
Log.d(TAG, "onDragItemFinished(holder = " + holder + ", result = " + result + ")");
}
if (holder != null && holder != mDraggingItem) {
throw new IllegalStateException("onDragItemFinished() - may be a bug (mDraggingItem != holder)");
}
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
return;
}
if (result && (mDraggingItemCurrentPosition != mDraggingItemInitialPosition)) {
// apply to wrapped adapter
DraggableItemAdapter adapter = WrapperAdapterUtils.findWrappedAdapter(
getWrappedAdapter(), DraggableItemAdapter.class);
adapter.onMoveItem(mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
}
mDraggingItemInitialPosition = RecyclerView.NO_POSITION;
mDraggingItemCurrentPosition = RecyclerView.NO_POSITION;
mDraggableRange = null;
mDraggingItemId = RecyclerView.NO_ID;
mDraggingItem = null;
notifyDataSetChanged();
}
@Override
public void onViewRecycled(VH holder) {
if (isDragging()) {
if (holder == mDraggingItem) {
if (LOCAL_LOGI) {
Log.i(TAG, "a view holder object which is bound to currently dragging item is recycled");
}
mDraggingItem = null;
mDragDropManager.onDraggingItemViewRecycled();
}
}
super.onViewRecycled(holder);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
boolean canStartDrag(RecyclerView.ViewHolder holder, int position, int x, int y) {
if (LOCAL_LOGV) {
Log.v(TAG, "canStartDrag(holder = " + holder + ", position = " + position + ", x = " + x + ", y = " + y + ")");
}
return mDraggableItemAdapter.onCheckCanStartDrag(holder, position, x, y);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/
@SuppressWarnings("unchecked")
ItemDraggableRange getItemDraggableRange(RecyclerView.ViewHolder holder, int position) {
if (LOCAL_LOGV) {
Log.v(TAG, "getItemDraggableRange(holder = " + holder + ", position = " + position + ")");
}
return mDraggableItemAdapter.onGetItemDraggableRange(holder, position);
}
// NOTE: This method is called from RecyclerViewDragDropManager
/*package*/ void moveItem(int fromPosition, int toPosition) {
if (LOCAL_LOGD) {
Log.d(TAG, "onMoveItem(fromPosition = " + fromPosition + ", toPosition = " + toPosition + ")");
}
if (DEBUG_BYPASS_MOVE_OPERATION_MODE) {
mDraggableItemAdapter.onMoveItem(fromPosition, toPosition);
return;
}
final int origFromPosition = convertToOriginalPosition(
fromPosition, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
if (origFromPosition != mDraggingItemInitialPosition) {
throw new IllegalStateException(
"onMoveItem() - may be a bug or has duplicate IDs --- " +
"mDraggingItemInitialPosition = " + mDraggingItemInitialPosition + ", " +
"mDraggingItemCurrentPosition = " + mDraggingItemCurrentPosition + ", " +
"origFromPosition = " + origFromPosition + ", " +
"fromPosition = " + fromPosition + ", " +
"toPosition = " + toPosition);
}
mDraggingItemCurrentPosition = toPosition;
// NOTE:
// Don't move items in wrapped adapter here.
// notify to observers
notifyItemMoved(fromPosition, toPosition);
}
protected boolean isDragging() {
return mDragDropManager.isDragging();
}
private static void safeUpdateFlags(RecyclerView.ViewHolder holder, int flags) {
if (!(holder instanceof DraggableItemViewHolder)) {
return;
}
final DraggableItemViewHolder holder2 = (DraggableItemViewHolder) holder;
final int curFlags = holder2.getDragStateFlags();
final int mask = ~RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED;
// append UPDATED flag
if ((curFlags == STATE_FLAG_INITIAL_VALUE) || (((curFlags ^ flags) & mask) != 0)) {
flags |= RecyclerViewDragDropManager.STATE_FLAG_IS_UPDATED;
}
((DraggableItemViewHolder) holder).setDragStateFlags(flags);
}
private static DraggableItemAdapter getDraggableItemAdapter(RecyclerView.Adapter adapter) {
return WrapperAdapterUtils.findWrappedAdapter(adapter, DraggableItemAdapter.class);
}
private int getOriginalPosition(int position) {
int correctedPosition;
if (isDragging()) {
correctedPosition = convertToOriginalPosition(
position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition);
} else {
correctedPosition = position;
}
return correctedPosition;
}
//
// SwipeableItemAdapter implementations
//
@Override
public int onGetSwipeReactionType(VH holder, int position, int x, int y) {
RecyclerView.Adapter adapter = getWrappedAdapter();
if (!(adapter instanceof SwipeableItemAdapter)) {
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
}
int correctedPosition = getOriginalPosition(position);
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
return swipeableItemAdapter.onGetSwipeReactionType(holder, correctedPosition, x, y);
}
@Override
public void onSetSwipeBackground(VH holder, int position, int type) {
RecyclerView.Adapter adapter = getWrappedAdapter();
if (!(adapter instanceof SwipeableItemAdapter)) {
return;
}
int correctedPosition = getOriginalPosition(position);
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
swipeableItemAdapter.onSetSwipeBackground(holder, correctedPosition, type);
}
@Override
public int onSwipeItem(VH holder, int position, int result) {
RecyclerView.Adapter adapter = getWrappedAdapter();
if (!(adapter instanceof SwipeableItemAdapter)) {
return RecyclerViewSwipeManager.AFTER_SWIPE_REACTION_DEFAULT;
}
int correctedPosition = getOriginalPosition(position);
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
return swipeableItemAdapter.onSwipeItem(holder, correctedPosition, result);
}
@Override
public void onPerformAfterSwipeReaction(VH holder, int position, int result, int reaction) {
RecyclerView.Adapter adapter = getWrappedAdapter();
if (!(adapter instanceof SwipeableItemAdapter)) {
return;
}
int correctedPosition = getOriginalPosition(position);
SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter;
swipeableItemAdapter.onPerformAfterSwipeReaction(holder, correctedPosition, result, reaction);
}
}