package gem.kevin.widget;
import gem.com.readystatesoftware.viewbadger.BadgeView;
import gem.kevin.util.ui.NamedColor;
import java.util.LinkedList;
import android.content.ClipData;
import android.content.Context;
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ImageView;
public class ActionModeDraggableView extends FrameLayout implements
DropAcceptable, Openable {
public static class DragContextInfo {
public Object data = null;
public ViewParent parentView = null;
public int positionAsChild = POSITION_UNORDERED;
public ImageView draggingView = null;
public ImageView dragReadyView = null;
public View normalView = null;
}
public interface DragProgressDelegate {
public DragShadowBuilder generateDragShadow(
ActionModeDraggableView dragSource);
public void interruptDragPreparation(
final ActionModeDraggableView dragSource);
public boolean isDragOpDelegated(
final ActionModeDraggableView dragSource);
public boolean isDragPrepared(final ActionModeDraggableView dragSource);
public void notifyAutoDragStarted(
final ActionModeDraggableView dragSource);
public void prepareDrag(final ActionModeDraggableView dragSource);
public void revertDragPreparation(
final ActionModeDraggableView dragSource);
}
private static final String TAG = "gem-kevin_ActionModeDraggableView";
public static final long HOVER_DECISION_COUNTDOWN = 1000L;
public static final long DRAG_DECISION_INTERVAL = 300L;
public static final int POSITION_UNORDERED = -1;
public static final int COLOR_DROP_AVAILABLE = 0x66000000 | NamedColor.RoyalBlue;
public static final int COLOR_DROP_NA_OPEN_AVAILABLE = 0x50000000 | NamedColor.LightCoral;
public static final int APLAH_TRANSPARENT = 0x00ffffff;
private DragContextInfo mDragContextInfo;
private BadgeView mBadgeView;
private DragProgressDelegate mDragProgressDelegate;
private LinkedList<Object> mDragBuddies;
private boolean mInActionMode = false;
private boolean mDragable = true;
private boolean mIsNewDragOp = true;
private boolean mDragging = false;
private boolean mDragReady = false;
private boolean mDragCanceled = false;
private OnDropDelegate mOnDropDelegate;
private CountDownTimer mHoverCountDownTimer; // Do a lazy initialization
private OpenDelegate mOpenDelegate;
// TODO: READY TO DRAG to solve touch point up outside the view.
private long mSavedDownTime;
public ActionModeDraggableView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean canAcceptDrop() {
if (mOnDropDelegate != null) {
return mOnDropDelegate.checkDropDataAcceptable(null, this);
}
return false;
}
@Override
public boolean canAppendContent() {
return canAcceptDrop();
}
@Override
public boolean canOpen() {
if (mOpenDelegate != null) {
return mOpenDelegate.checkOpenable(this);
}
return false;
}
public void doClickInActionMode() {
if (mInActionMode && mDragContextInfo != null
&& mDragContextInfo.parentView instanceof AbsListView) {
Log.i(TAG, "Do click in action mode.");
if (mDragReady) {
if (mDragProgressDelegate != null) {
mDragProgressDelegate.revertDragPreparation(this);
}
mDragReady = false;
} else {
// Due to the interactive ability of this widget
AbsListView parentListView = (AbsListView) mDragContextInfo.parentView;
int position = mDragContextInfo.positionAsChild;
if (parentListView != null && position >= 0) {
boolean lastChecked = parentListView
.isItemChecked(position);
parentListView.setItemChecked(position, !lastChecked);
parentListView.invalidate();
}
}
}
}
public boolean doDrag(ClipData data, DragShadowBuilder shadow) {
mDragging = startDrag(data, shadow, null, 0);
return mDragging;
}
public boolean doDrag(DragShadowBuilder shadow) {
Log.i(TAG, "do drag.");
ClipData clipData;
if (getDragContextInfo().data instanceof ClipData) {
clipData = (ClipData) getDragContextInfo().data;
mDragging = startDrag(clipData, shadow, null, 0);
} else {
mDragging = startDrag(null, shadow, null, 0);
}
return mDragging;
}
public BadgeView getBadgeView() {
return mBadgeView;
}
public LinkedList<Object> getDragBuddies() {
return mDragBuddies;
}
public DragContextInfo getDragContextInfo() {
if (mDragContextInfo == null) {
mDragContextInfo = getDefaultDragContextInfo();
}
return mDragContextInfo;
}
public DragProgressDelegate getDragDropDelegate() {
return mDragProgressDelegate;
}
public OnDropDelegate getOnDropDelegate() {
return mOnDropDelegate;
}
public boolean isDragCanceled() {
return mDragCanceled;
}
public boolean isDragReady() {
return mDragReady;
}
@Override
public boolean onDragEnded(DragEvent event) {
boolean success = event.getResult();
if (mOnDropDelegate != null) {
if (!success) {
mOnDropDelegate.onDropFailed(this);
}
}
return false;
}
@Override
public boolean onDragEntered(DragEvent event) {
boolean canAcceptDrop = canAcceptDrop();
boolean canOpen = canOpen();
// Set background color to indicate
// it can be drop or can be opened
if (canAcceptDrop) {
setBackgroundColor(COLOR_DROP_AVAILABLE);
} else {
if (canOpen) {
setBackgroundColor(COLOR_DROP_NA_OPEN_AVAILABLE);
}
}
// If it can't open, then we do not call onDragHover on it.
// Bind behaviors 'open' and 'drag hover' to a single logic
// for performance purpose.
if (canOpen) {
if (mHoverCountDownTimer == null) {
mHoverCountDownTimer = new CountDownTimer(
HOVER_DECISION_COUNTDOWN, HOVER_DECISION_COUNTDOWN / 10) {
@Override
public void onFinish() {
onHover();
}
@Override
public void onTick(long millisUntilFinished) {
// Do nothing
Log.i(TAG, "Tick: " + millisUntilFinished);
}
};
}
mHoverCountDownTimer.start();
}
return canAcceptDrop;
}
@Override
public boolean onDragEvent(DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
Log.i(TAG, "ACDV Drag Started.");
return onDragStarted(event);
case DragEvent.ACTION_DRAG_ENTERED:
Log.i(TAG, "ACDV Drag entered.");
return onDragEntered(event);
case DragEvent.ACTION_DRAG_LOCATION:
return onDragMoveOn(event);
case DragEvent.ACTION_DRAG_EXITED:
Log.i(TAG, "ACDV Drag exited.");
return onDragExited(event);
case DragEvent.ACTION_DROP:
Log.i(TAG, "ACDV Drag droped.");
return onDrop(event);
case DragEvent.ACTION_DRAG_ENDED:
Log.i(TAG, "ACDV Drag droped.");
return onDragEnded(event);
}
return super.onDragEvent(event);
}
@Override
public boolean onDragExited(DragEvent event) {
setBackgroundColor(APLAH_TRANSPARENT);
if (mHoverCountDownTimer != null) {
mHoverCountDownTimer.cancel();
}
return false;
}
@Override
public boolean onDragMoveOn(DragEvent event) {
// do nothing
return true;
}
@Override
public boolean onDragStarted(DragEvent event) {
return true;
}
@Override
public boolean onDrop(DragEvent event) {
setBackgroundColor(APLAH_TRANSPARENT);
if (mHoverCountDownTimer != null) {
mHoverCountDownTimer.cancel();
}
if (mOnDropDelegate != null) {
Object data = mOnDropDelegate.generateDropData();
boolean result = true;
if (mOnDropDelegate.checkDropDataAcceptable(data, this)) {
result = mOnDropDelegate.handleDrop(data, this);
if (result) {
mOnDropDelegate.onDropSuccess(this);
} else {
mOnDropDelegate.onDropFailed(this);
}
} else {
if (mOnDropDelegate.shouldDispathUnacceptableDropToParent(this)) {
Log.i(TAG, "dispatch unacceptable drop to parent.");
result = super.onDragEvent(event);
} else {
// Only notify drop denial when the view has indicated it could possibly
// accept drop (Actual it could not)
// Task 'canOpen' as a possible drop indicator because it also changed the UI
// as the 'canAcceptDrop' did. User might tend to think it is dropable.
if (canOpen()) {
mOnDropDelegate.notifyDropDataDenied(data, this);
}
mOnDropDelegate.onDropFailed(this);
}
}
return result;
}
return false;
}
@Override
public void onHover() {
if (mOnDropDelegate != null) {
mOnDropDelegate.onHover(this);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent");
if (!mDragable) {
return super.onTouchEvent(event);
}
boolean result = super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsNewDragOp = true;
mDragging = false;
mDragCanceled = false;
if (mInActionMode) {
mSavedDownTime = System.currentTimeMillis();
result = true;
}
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "ACTION MOVE" + " parent-width: " + this.getWidth()
+ " parent-height: " + this.getHeight() + " touch x: "
+ event.getX() + " touch y: " + event.getY());
if (mInActionMode) {
long interval = System.currentTimeMillis() - mSavedDownTime;
Log.i(TAG, "Interval is: " + interval);
if (interval > DRAG_DECISION_INTERVAL) {
if (mIsNewDragOp) {
prepareDrag();
} else {
if (mDragProgressDelegate != null) {
if (!mDragProgressDelegate.isDragOpDelegated(this)) {
Log.i(TAG, "Drag is not delegated.");
if (mDragProgressDelegate.isDragPrepared(this)
&& !mDragging) {
Log.i(TAG, "Drag is prepared, do drag!");
doDrag(mDragProgressDelegate
.generateDragShadow(this));
mDragProgressDelegate
.notifyAutoDragStarted(this);
}
} else {
Log.i(TAG, "Drag is delegated.");
}
} else {
if (!mDragging) {
doDrag(new DragShadowBuilder(this));
}
}
}
}
result = true;
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "ACTION up");
mIsNewDragOp = true;
if (mInActionMode) {
long interval = System.currentTimeMillis() - mSavedDownTime;
Log.i(TAG, "Interval is: " + interval);
if (interval > DRAG_DECISION_INTERVAL) {
if (mDragProgressDelegate != null) {
if (!mDragProgressDelegate.isDragPrepared(this)) {
mDragProgressDelegate
.interruptDragPreparation(this);
}
mDragReady = false;
Log.i(TAG, "revert drag prepartion for action up.");
mDragProgressDelegate.revertDragPreparation(this);
}
} else {
doClickInActionMode();
}
} else {
doClickInActionMode();
}
result = true;
break;
}
if (mInActionMode) {
return result;
} else {
return super.onTouchEvent(event);
}
}
@Override
public void open() {
if (mOpenDelegate != null) {
mOpenDelegate.doOpen(this);
}
}
public void setBadgeView(BadgeView badge) {
mBadgeView = badge;
}
@Override
public void markChildCanGetDragEvent(boolean childCanGetDragEvent) {
// this is a final DropAcceptable,
// no child to get drag events
}
public void setDragable(boolean dragable) {
mDragable = dragable;
}
public void setDragBuddies(LinkedList<Object> buddies) {
mDragBuddies = buddies;
}
public void setDragContextInfo(DragContextInfo info) {
mDragContextInfo = info;
}
public void setDragDropDelegate(DragProgressDelegate delegate) {
mDragProgressDelegate = delegate;
}
public void setDragReady(boolean dragReady) {
mDragReady = dragReady;
}
public void setInActionMode(boolean inActionMode) {
mInActionMode = inActionMode;
}
public void setOnDropDelegate(OnDropDelegate delegate) {
mOnDropDelegate = delegate;
}
public void setOpenDelegate(OpenDelegate delegate) {
mOpenDelegate = delegate;
}
private DragContextInfo getDefaultDragContextInfo() {
DragContextInfo info = new DragContextInfo();
info.parentView = getParent();
return info;
}
private void prepareDrag() {
if (!mDragReady) {
if (mDragProgressDelegate != null) {
mDragProgressDelegate.prepareDrag(this);
}
} else {
Log.i(TAG, "Drag is already prepared.");
}
mIsNewDragOp = false;
}
}