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; } }