package com.flipkart.chatheads.ui;
import android.graphics.Point;
import android.os.Bundle;
import android.support.annotation.NonNull;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringChain;
import com.facebook.rebound.SpringListener;
import com.flipkart.chatheads.ChatHeadUtils;
import java.io.Serializable;
import java.util.List;
public class MinimizedArrangement<T extends Serializable> extends ChatHeadArrangement {
public static final String BUNDLE_HERO_INDEX_KEY = "hero_index";
public static final String BUNDLE_HERO_RELATIVE_X_KEY = "hero_relative_x";
public static final String BUNDLE_HERO_RELATIVE_Y_KEY = "hero_relative_y";
private static int MAX_VELOCITY_FOR_IDLING;
private static int MIN_VELOCITY_TO_POSITION_BACK;
private float DELTA = 0;
private float currentDelta = 0;
private int idleStateX = Integer.MIN_VALUE;
private int idleStateY = Integer.MIN_VALUE;
private int maxWidth;
private int maxHeight;
private boolean hasActivated = false;
private ChatHeadManager<T> manager;
private SpringChain horizontalSpringChain;
private SpringChain verticalSpringChain;
private ChatHead hero;
private double relativeXPosition = -1;
private double relativeYPosition = -1;
private Bundle extras;
private SpringListener horizontalHeroListener = new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
currentDelta = (float) ((float) DELTA * (maxWidth / 2 - spring.getCurrentValue()) / (maxWidth / 2));
if (horizontalSpringChain != null)
horizontalSpringChain.getControlSpring().setCurrentValue(spring.getCurrentValue());
}
@Override
public void onSpringAtRest(Spring spring) {
super.onSpringAtRest(spring);
if (isTransitioning) {
isTransitioning = false;
}
}
};
private SpringListener verticalHeroListener = new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
if (verticalSpringChain != null)
verticalSpringChain.getControlSpring().setCurrentValue(spring.getCurrentValue());
}
@Override
public void onSpringAtRest(Spring spring) {
super.onSpringAtRest(spring);
if (isTransitioning) {
isTransitioning = false;
}
}
};
private boolean isTransitioning;
public MinimizedArrangement(ChatHeadManager manager) {
this.manager = manager;
DELTA = ChatHeadUtils.dpToPx(this.manager.getContext(), 5);
}
public void setIdleStateX(int idleStateX) {
this.idleStateX = idleStateX;
}
public void setIdleStateY(int idleStateY) {
this.idleStateY = idleStateY;
}
public Point getIdleStatePosition() {
return new Point(idleStateX, idleStateY);
}
@Override
public void setContainer(ChatHeadManager container) {
this.manager = container;
}
@Override
public void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated) {
isTransitioning = true;
if (horizontalSpringChain != null || verticalSpringChain != null) {
onDeactivate(maxWidth, maxHeight);
}
MIN_VELOCITY_TO_POSITION_BACK = ChatHeadUtils.dpToPx(container.getDisplayMetrics(), 600);
MAX_VELOCITY_FOR_IDLING = ChatHeadUtils.dpToPx(container.getDisplayMetrics(), 1);
int heroIndex = 0;
this.extras = extras;
if (extras != null) {
heroIndex = extras.getInt(BUNDLE_HERO_INDEX_KEY, -1);
relativeXPosition = extras.getDouble(BUNDLE_HERO_RELATIVE_X_KEY, -1);
relativeYPosition = extras.getDouble(BUNDLE_HERO_RELATIVE_Y_KEY, -1);
}
List<ChatHead> chatHeads = container.getChatHeads();
if (heroIndex < 0 || heroIndex > chatHeads.size() - 1) {
heroIndex = 0;
}
int zIndex = 0;
if (heroIndex < chatHeads.size()) {
hero = chatHeads.get(heroIndex);
hero.setHero(true);
horizontalSpringChain = SpringChain.create();
verticalSpringChain = SpringChain.create();
for (int i = 0; i < chatHeads.size(); i++) {
final ChatHead chatHead = chatHeads.get(i);
if (chatHead != hero) {
chatHead.setHero(false);
horizontalSpringChain.addSpring(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
int index = horizontalSpringChain.getAllSprings().indexOf(spring);
int diff = index - horizontalSpringChain.getAllSprings().size() + 1;
chatHead.getHorizontalSpring().setCurrentValue(spring.getCurrentValue() + diff * currentDelta);
}
});
Spring currentSpring = horizontalSpringChain.getAllSprings().get(horizontalSpringChain.getAllSprings().size() - 1);
currentSpring.setCurrentValue(chatHead.getHorizontalSpring().getCurrentValue());
verticalSpringChain.addSpring(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
chatHead.getVerticalSpring().setCurrentValue(spring.getCurrentValue());
}
});
currentSpring = verticalSpringChain.getAllSprings().get(verticalSpringChain.getAllSprings().size() - 1);
currentSpring.setCurrentValue(chatHead.getVerticalSpring().getCurrentValue());
manager.getChatHeadContainer().bringToFront(chatHead);
zIndex++;
}
}
if (relativeXPosition == -1) {
idleStateX = container.getConfig().getInitialPosition().x;
} else {
idleStateX = (int) (relativeXPosition * maxWidth);
}
if (relativeYPosition == -1) {
idleStateY = container.getConfig().getInitialPosition().y;
} else {
idleStateY = (int) (relativeYPosition * maxHeight);
}
idleStateX = stickToEdgeX(idleStateX, maxWidth, hero);
if (hero != null && hero.getHorizontalSpring() != null && hero.getVerticalSpring() != null) {
manager.getChatHeadContainer().bringToFront(hero);
horizontalSpringChain.addSpring(new SimpleSpringListener() {
});
verticalSpringChain.addSpring(new SimpleSpringListener() {
});
horizontalSpringChain.setControlSpringIndex(chatHeads.size() - 1);
verticalSpringChain.setControlSpringIndex(chatHeads.size() - 1);
hero.getHorizontalSpring().addListener(horizontalHeroListener);
hero.getVerticalSpring().addListener(verticalHeroListener);
hero.getHorizontalSpring().setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
if (hero.getHorizontalSpring().getCurrentValue() == idleStateX) {
//safety check so that spring animates correctly
hero.getHorizontalSpring().setCurrentValue(idleStateX - 1, true);
}
if (animated) {
hero.getHorizontalSpring().setEndValue(idleStateX);
} else {
hero.getHorizontalSpring().setCurrentValue(idleStateX, true);
}
hero.getVerticalSpring().setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
if (hero.getVerticalSpring().getCurrentValue() == idleStateY) {
//safety check so that spring animates correctly
hero.getVerticalSpring().setCurrentValue(idleStateY - 1, true);
}
if (animated) {
hero.getVerticalSpring().setEndValue(idleStateY);
} else {
hero.getVerticalSpring().setCurrentValue(idleStateY, true);
}
}
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
container.getCloseButton().setEnabled(true);
}
hasActivated = true;
// if(springsHolder.getActiveHorizontalSpring()!=null && springsHolder.getActiveVerticalSpring()!=null) {
// handleTouchUp(null, 0, 0, springsHolder.getActiveHorizontalSpring(), springsHolder.getActiveVerticalSpring(), true);
// }
}
private int stickToEdgeX(int currentX, int maxWidth, ChatHead chatHead) {
if (maxWidth - currentX < currentX) {
// this means right edge is closer
return maxWidth - chatHead.getMeasuredWidth();
} else {
return 0;
}
}
@Override
public void onChatHeadAdded(ChatHead chatHead, boolean animated) {
if (hero != null && hero.getHorizontalSpring() != null && hero.getVerticalSpring() != null) {
chatHead.getHorizontalSpring().setCurrentValue(hero.getHorizontalSpring().getCurrentValue() - currentDelta);
chatHead.getVerticalSpring().setCurrentValue(hero.getVerticalSpring().getCurrentValue());
}
onActivate(manager, getRetainBundle(), maxWidth, maxHeight, animated);
}
@Override
public void onChatHeadRemoved(ChatHead removed) {
manager.detachView(removed,manager.getArrowLayout());
manager.removeView(removed, manager.getArrowLayout());
if (removed == hero) {
hero = null;
}
onActivate(manager, null, maxWidth, maxHeight, true);
}
@Override
public void onCapture(ChatHeadManager container, ChatHead activeChatHead) {
// we dont care about the active ones
container.removeAllChatHeads(true);
}
@Override
public void selectChatHead(ChatHead chatHead) {
//manager.toggleArrangement();
}
@Override
public void onDeactivate(int maxWidth, int maxHeight) {
hasActivated = false;
if (hero != null) {
hero.getHorizontalSpring().removeListener(horizontalHeroListener);
hero.getVerticalSpring().removeListener(verticalHeroListener);
}
if (horizontalSpringChain != null) {
List<Spring> allSprings = horizontalSpringChain.getAllSprings();
for (Spring spring : allSprings) {
spring.destroy();
}
}
if (verticalSpringChain != null) {
List<Spring> allSprings = verticalSpringChain.getAllSprings();
for (Spring spring : allSprings) {
spring.destroy();
}
}
horizontalSpringChain = null;
verticalSpringChain = null;
}
@Override
public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVelocity, Spring
activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging) {
settleToClosest(activeChatHead, xVelocity, yVelocity);
if (!wasDragging) {
boolean handled = manager.onItemSelected(activeChatHead);
if (!handled) {
deactivate();
return false;
}
}
return true;
}
private void settleToClosest(ChatHead activeChatHead, int xVelocity, int yVelocity) {
Spring activeHorizontalSpring = activeChatHead.getHorizontalSpring();
Spring activeVerticalSpring = activeChatHead.getVerticalSpring();
if (activeChatHead.getState() == ChatHead.State.FREE) {
if (Math.abs(xVelocity) < ChatHeadUtils.dpToPx(manager.getDisplayMetrics(), 50)) {
if (activeHorizontalSpring.getCurrentValue() < (maxWidth - activeHorizontalSpring.getCurrentValue())) {
xVelocity = -1;
} else {
xVelocity = 1;
}
}
if (xVelocity < 0) {
int newVelocity = (int) (-activeHorizontalSpring.getCurrentValue() * SpringConfigsHolder.DRAGGING.friction);
if (xVelocity > newVelocity)
xVelocity = (newVelocity);
} else if (xVelocity > 0) {
int newVelocity = (int) ((maxWidth - activeHorizontalSpring.getCurrentValue() - manager.getConfig().getHeadWidth()) * SpringConfigsHolder.DRAGGING.friction);
if (newVelocity > xVelocity)
xVelocity = (newVelocity);
}
}
if (Math.abs(xVelocity) <= 1) {
// this is a hack. If both velocities are 0, onSprintUpdate is not called and the chat head remains whereever it is
// so we give a a negligible velocity to artificially fire onSpringUpdate
if (xVelocity < 0)
xVelocity = -1;
else
xVelocity = 1;
}
if (yVelocity == 0)
yVelocity = 1;
activeHorizontalSpring.setVelocity(xVelocity);
activeVerticalSpring.setVelocity(yVelocity);
}
private void deactivate() {
Bundle bundle = getBundleWithHero();
manager.setArrangement(MaximizedArrangement.class, bundle);
}
@NonNull
private Bundle getBundleWithHero() {
return getBundle(getHeroIndex());
}
private Bundle getBundle(int heroIndex) {
if(hero!=null) {
relativeXPosition = hero.getHorizontalSpring().getCurrentValue() * 1.0 / maxWidth;
relativeYPosition = hero.getVerticalSpring().getCurrentValue() * 1.0 / maxHeight;
}
Bundle bundle = extras;
if (bundle == null) {
bundle = new Bundle();
}
bundle.putInt(MaximizedArrangement.BUNDLE_HERO_INDEX_KEY, heroIndex);
bundle.putDouble(MinimizedArrangement.BUNDLE_HERO_RELATIVE_X_KEY, relativeXPosition);
bundle.putDouble(MinimizedArrangement.BUNDLE_HERO_RELATIVE_Y_KEY, relativeYPosition);
return bundle;
}
/**
* @return the index of the selected chat head a.k.a the hero
*/
@Override
public Integer getHeroIndex() {
return getHeroIndex(hero);
}
private Integer getHeroIndex(ChatHead hero) {
int heroIndex = 0;
List<ChatHead<T>> chatHeads = manager.getChatHeads();
int i = 0;
for (ChatHead chatHead : chatHeads) {
if (hero == chatHead) {
heroIndex = i;
}
i++;
}
return heroIndex;
}
@Override
public void onConfigChanged(ChatHeadConfig newConfig) {
}
@Override
public Bundle getRetainBundle() {
return getBundleWithHero();
}
@Override
public boolean canDrag(ChatHead chatHead) {
return true; //all chat heads are draggable
}
@Override
public void removeOldestChatHead() {
for (ChatHead<T> chatHead : manager.getChatHeads()) {
if (!chatHead.isSticky()) {
manager.removeChatHead(chatHead.getKey(), false);
break;
}
}
}
@Override
public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxWidth, int maxHeight, Spring spring, Spring activeHorizontalSpring, Spring activeVerticalSpring, int totalVelocity) {
/** This method does a bounds Check **/
double xVelocity = activeHorizontalSpring.getVelocity();
double yVelocity = activeVerticalSpring.getVelocity();
if (!isDragging && Math.abs(totalVelocity) < MIN_VELOCITY_TO_POSITION_BACK && activeChatHead == hero) {
if (Math.abs(totalVelocity) < MAX_VELOCITY_FOR_IDLING && activeChatHead.getState() == ChatHead.State.FREE && hasActivated) {
setIdleStateX((int) activeHorizontalSpring.getCurrentValue());
setIdleStateY((int) activeVerticalSpring.getCurrentValue());
}
if (spring == activeHorizontalSpring) {
double xPosition = activeHorizontalSpring.getCurrentValue();
if (xPosition + manager.getConfig().getHeadWidth() > maxWidth && activeHorizontalSpring.getVelocity() > 0) {
//outside the right bound
//System.out.println("outside the right bound !! xPosition = " + xPosition);
int newPos = maxWidth - manager.getConfig().getHeadWidth();
activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeHorizontalSpring.setEndValue(newPos);
} else if (xPosition < 0 && activeHorizontalSpring.getVelocity() < 0) {
//outside the left bound
//System.out.println("outside the left bound !! xPosition = " + xPosition);
activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeHorizontalSpring.setEndValue(0);
} else {
//within bound
}
} else if (spring == activeVerticalSpring) {
double yPosition = activeVerticalSpring.getCurrentValue();
if (yPosition + manager.getConfig().getHeadWidth() > maxHeight && activeVerticalSpring.getVelocity() > 0) {
//outside the bottom bound
//System.out.println("outside the bottom bound !! yPosition = " + yPosition);
activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeVerticalSpring.setEndValue(maxHeight - manager.getConfig().getHeadHeight());
} else if (yPosition < 0 && activeVerticalSpring.getVelocity() < 0) {
//outside the top bound
//System.out.println("outside the top bound !! yPosition = " + yPosition);
activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeVerticalSpring.setEndValue(0);
} else {
//within bound
}
}
}
if (!isDragging && activeChatHead == hero) {
/** Capturing check **/
int[] coords = manager.getChatHeadCoordsForCloseButton(activeChatHead);
double distanceCloseButtonFromHead = manager.getDistanceCloseButtonFromHead((float) activeHorizontalSpring.getCurrentValue() + manager.getConfig().getHeadWidth() / 2, (float) activeVerticalSpring.getCurrentValue() + manager.getConfig().getHeadHeight() / 2);
if (distanceCloseButtonFromHead < activeChatHead.CLOSE_ATTRACTION_THRESHOLD && activeHorizontalSpring.getSpringConfig() == SpringConfigsHolder.DRAGGING && activeVerticalSpring.getSpringConfig() == SpringConfigsHolder.DRAGGING) {
activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING);
activeChatHead.setState(ChatHead.State.CAPTURED);
}
if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.CAPTURING) {
activeHorizontalSpring.setAtRest();
activeVerticalSpring.setAtRest();
activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.CAPTURING);
activeVerticalSpring.setSpringConfig(SpringConfigsHolder.CAPTURING);
activeHorizontalSpring.setEndValue(coords[0]);
activeVerticalSpring.setEndValue(coords[1]);
}
if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeVerticalSpring.isAtRest()) {
manager.getCloseButton().disappear(false, true);
manager.captureChatHeads(activeChatHead);
}
if (!activeVerticalSpring.isAtRest() && !isTransitioning) {
manager.getCloseButton().appear();
} else {
manager.getCloseButton().disappear(true, true);
}
}
}
@Override
public void bringToFront(ChatHead chatHead) {
Bundle b = getBundle(getHeroIndex(chatHead));
onActivate(manager, b, manager.getMaxWidth(), manager.getMaxHeight(), true);
}
@Override
public void onReloadFragment(ChatHead chatHead) {
// nothing to do
}
@Override
public boolean shouldShowCloseButton(ChatHead chatHead) {
return true;
}
}