package com.flipkart.chatheads.ui; import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.widget.ImageView; import com.facebook.rebound.SimpleSpringListener; import com.facebook.rebound.Spring; import com.facebook.rebound.SpringListener; import com.facebook.rebound.SpringSystem; import com.flipkart.chatheads.ChatHeadUtils; import java.io.Serializable; /** * Created by kirankumar on 10/02/15. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ChatHead<T extends Serializable> extends ImageView implements SpringListener { final int CLOSE_ATTRACTION_THRESHOLD = ChatHeadUtils.dpToPx(getContext(), 110); private final int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); private final float DELTA = ChatHeadUtils.dpToPx(getContext(), 10); private ChatHeadManager manager; private SpringSystem springSystem; private boolean isSticky = false; private State state; private T key; private float downX = -1; private float downY = -1; private VelocityTracker velocityTracker; private boolean isDragging; private float downTranslationX; private float downTranslationY; private int unreadCount = 0; private SpringListener xPositionListener; private SpringListener yPositionListener; private Spring scaleSpring; private Spring xPositionSpring; private Spring yPositionSpring; private Bundle extras; private ImageView imageView; private boolean isHero; public ChatHead(Context context) { super(context); throw new IllegalArgumentException("This constructor cannot be used"); } public ChatHead(Context context, AttributeSet attrs) { super(context, attrs); throw new IllegalArgumentException("This constructor cannot be used"); } public ChatHead(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); throw new IllegalArgumentException("This constructor cannot be used"); } public ChatHead(ChatHeadManager manager, SpringSystem springsHolder, Context context, boolean isSticky) { super(context); this.manager = manager; this.springSystem = springsHolder; this.isSticky = isSticky; init(); } public boolean isHero() { return isHero; } public void setHero(boolean hero) { isHero = hero; } public Bundle getExtras() { return extras; } public void setExtras(Bundle extras) { this.extras = extras; } public Spring getHorizontalSpring() { return xPositionSpring; } public Spring getVerticalSpring() { return yPositionSpring; } public boolean isSticky() { return isSticky; } private void init() { xPositionListener = new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); manager.getChatHeadContainer().setViewX(ChatHead.this, (int)spring.getCurrentValue()); } @Override public void onSpringAtRest(Spring spring) { super.onSpringAtRest(spring); } }; xPositionSpring = springSystem.createSpring(); xPositionSpring.addListener(xPositionListener); xPositionSpring.addListener(this); yPositionListener = new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); manager.getChatHeadContainer().setViewY(ChatHead.this, (int)spring.getCurrentValue()); } @Override public void onSpringAtRest(Spring spring) { super.onSpringAtRest(spring); } }; yPositionSpring = springSystem.createSpring(); yPositionSpring.addListener(yPositionListener); yPositionSpring.addListener(this); scaleSpring = springSystem.createSpring(); scaleSpring.addListener(new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); double currentValue = spring.getCurrentValue(); setScaleX((float) currentValue); setScaleY((float) currentValue); } }); scaleSpring.setCurrentValue(1).setAtRest(); } public int getUnreadCount() { return unreadCount; } public void setUnreadCount(int unreadCount) { if (unreadCount != this.unreadCount) { manager.reloadDrawable(key); } this.unreadCount = unreadCount; } public State getState() { return state; } public void setState(State state) { this.state = state; } public T getKey() { return key; } public void setKey(T key) { this.key = key; } @Override public void onSpringUpdate(Spring spring) { if (xPositionSpring != null && yPositionSpring != null) { Spring activeHorizontalSpring = xPositionSpring; Spring activeVerticalSpring = yPositionSpring; if (spring != activeHorizontalSpring && spring != activeVerticalSpring) return; int totalVelocity = (int) Math.hypot(activeHorizontalSpring.getVelocity(), activeVerticalSpring.getVelocity()); if (manager.getActiveArrangement() != null) manager.getActiveArrangement().onSpringUpdate(this, isDragging, manager.getMaxWidth(), manager.getMaxHeight(), spring, activeHorizontalSpring, activeVerticalSpring, totalVelocity); } } @Override public void onSpringAtRest(Spring spring) { if (manager.getListener() != null) manager.getListener().onChatHeadAnimateEnd(this); } @Override public void onSpringActivate(Spring spring) { if (manager.getListener() != null) manager.getListener().onChatHeadAnimateStart(this); } @Override public void onSpringEndStateChange(Spring spring) { } public SpringListener getHorizontalPositionListener() { return xPositionListener; } public SpringListener getVerticalPositionListener() { return yPositionListener; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { super.onTouchEvent(event); if(xPositionSpring==null || yPositionSpring==null) return false; //Chathead view will set the correct active springs on touch Spring activeHorizontalSpring = xPositionSpring; Spring activeVerticalSpring = yPositionSpring; int action = event.getAction(); float rawX = event.getRawX(); float rawY = event.getRawY(); float offsetX = rawX - downX; float offsetY = rawY - downY; boolean showCloseButton = manager.getActiveArrangement().shouldShowCloseButton(this); event.offsetLocation(manager.getChatHeadContainer().getViewX(this), manager.getChatHeadContainer().getViewY(this)); if (action == MotionEvent.ACTION_DOWN) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } else { velocityTracker.clear(); } activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); setState(ChatHead.State.FREE); downX = rawX; downY = rawY; downTranslationX = (float) activeHorizontalSpring.getCurrentValue(); downTranslationY = (float) activeVerticalSpring.getCurrentValue(); scaleSpring.setEndValue(.9f); activeHorizontalSpring.setAtRest(); activeVerticalSpring.setAtRest(); velocityTracker.addMovement(event); } else if (action == MotionEvent.ACTION_MOVE) { if (Math.hypot(offsetX, offsetY) > touchSlop) { isDragging = true; if (showCloseButton) { manager.getCloseButton().appear(); } } velocityTracker.addMovement(event); if (isDragging) { manager.getCloseButton().pointTo(rawX, rawY); if (manager.getActiveArrangement().canDrag(this)) { double distanceCloseButtonFromHead = manager.getDistanceCloseButtonFromHead(rawX, rawY); if (distanceCloseButtonFromHead < CLOSE_ATTRACTION_THRESHOLD && showCloseButton) { setState(ChatHead.State.CAPTURED); activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); int[] coords = manager.getChatHeadCoordsForCloseButton(this); activeHorizontalSpring.setEndValue(coords[0]); activeVerticalSpring.setEndValue(coords[1]); manager.getCloseButton().onCapture(); } else { setState(ChatHead.State.FREE); activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.DRAGGING); activeVerticalSpring.setSpringConfig(SpringConfigsHolder.DRAGGING); activeHorizontalSpring.setCurrentValue(downTranslationX + offsetX); activeVerticalSpring.setCurrentValue(downTranslationY + offsetY); manager.getCloseButton().onRelease(); } velocityTracker.computeCurrentVelocity(1000); } } } else { if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { boolean wasDragging = isDragging; activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.DRAGGING); activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.DRAGGING); isDragging = false; scaleSpring.setEndValue(1); int xVelocity = (int) velocityTracker.getXVelocity(); int yVelocity = (int) velocityTracker.getYVelocity(); velocityTracker.recycle(); velocityTracker = null; if(xPositionSpring!=null && yPositionSpring!=null) { boolean touchUpHandled = manager.getActiveArrangement().handleTouchUp(this, xVelocity, yVelocity, activeHorizontalSpring, activeVerticalSpring, wasDragging); } } } return true; } public void onRemove() { xPositionSpring.setAtRest(); xPositionSpring.removeAllListeners(); xPositionSpring.destroy(); xPositionSpring = null; yPositionSpring.setAtRest(); yPositionSpring.removeAllListeners(); yPositionSpring.destroy(); yPositionSpring = null; scaleSpring.setAtRest(); scaleSpring.removeAllListeners(); scaleSpring.destroy(); scaleSpring = null; } public void setImageDrawable(Drawable chatHeadDrawable) { super.setImageDrawable(chatHeadDrawable); } public enum State { FREE, CAPTURED } }