package com.flipkart.chatheads.ui; import android.annotation.TargetApi; import android.graphics.Point; import android.os.Build; import android.os.Bundle; import android.support.v4.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import com.facebook.rebound.SimpleSpringListener; import com.facebook.rebound.Spring; import com.flipkart.chatheads.ChatHeadUtils; import java.io.Serializable; import java.util.List; import java.util.Map; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class MaximizedArrangement<T extends Serializable> extends ChatHeadArrangement { public static final String BUNDLE_HERO_INDEX_KEY = "hero_index"; private static double MAX_DISTANCE_FROM_ORIGINAL; private static int MIN_VELOCITY_TO_POSITION_BACK; private final Map<ChatHead, Point> positions = new ArrayMap<>(); private ChatHeadManager<T> manager; private int maxWidth; private int maxHeight; private ChatHead currentChatHead = null; private UpArrowLayout arrowLayout; private int maxDistanceFromOriginal; private int topPadding; private boolean isActive = false; private boolean isTransitioning = false; private Bundle extras; public MaximizedArrangement(ChatHeadManager<T> manager) { this.manager = manager; } @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; this.manager = container; this.maxWidth = maxWidth; this.maxHeight = maxHeight; MIN_VELOCITY_TO_POSITION_BACK = ChatHeadUtils.dpToPx(container.getDisplayMetrics(), 50); MAX_DISTANCE_FROM_ORIGINAL = ChatHeadUtils.dpToPx(container.getContext(), 10); isActive = true; List<ChatHead> chatHeads = container.getChatHeads(); int heroIndex = 0; this.extras = extras; if (extras != null) heroIndex = extras.getInt(BUNDLE_HERO_INDEX_KEY, -1); if (heroIndex < 0 && currentChatHead != null) { heroIndex = getHeroIndex(); //this means we have a current chat head and we carry it forward } if (heroIndex < 0 || heroIndex > chatHeads.size() - 1) { heroIndex = 0; } if (chatHeads.size() > 0 && heroIndex < chatHeads.size()) { currentChatHead = chatHeads.get(heroIndex); maxDistanceFromOriginal = (int) MAX_DISTANCE_FROM_ORIGINAL; int spacing = container.getConfig().getHeadHorizontalSpacing(maxWidth, maxHeight); int widthPerHead = container.getConfig().getHeadWidth(); topPadding = ChatHeadUtils.dpToPx(container.getContext(), 5); int leftIndent = maxWidth - (chatHeads.size() * (widthPerHead + spacing)); for (int i = 0; i < chatHeads.size(); i++) { ChatHead chatHead = chatHeads.get(i); Spring horizontalSpring = chatHead.getHorizontalSpring(); int xPos = leftIndent + (i * (widthPerHead + spacing));//align right positions.put(chatHead, new Point(xPos, topPadding)); horizontalSpring.setAtRest(); horizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); horizontalSpring.setEndValue(xPos); if (!animated) { horizontalSpring.setCurrentValue(xPos); } Spring verticalSpring = chatHead.getVerticalSpring(); verticalSpring.setAtRest(); verticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); verticalSpring.setEndValue(topPadding); if (!animated) { verticalSpring.setCurrentValue(topPadding); } } container.getCloseButton().setEnabled(true); container.getOverlayView().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { deactivate(); } }); container.showOverlayView(animated); selectChatHead(currentChatHead); currentChatHead.getVerticalSpring().addListener(new SimpleSpringListener() { @Override public void onSpringAtRest(Spring spring) { super.onSpringAtRest(spring); if (isTransitioning) { isTransitioning = false; } currentChatHead.getVerticalSpring().removeListener(this); } }); currentChatHead.getHorizontalSpring().addListener(new SimpleSpringListener() { @Override public void onSpringAtRest(Spring spring) { super.onSpringAtRest(spring); if (isTransitioning) { isTransitioning = false; } currentChatHead.getHorizontalSpring().removeListener(this); } }); } } @Override public void onDeactivate(int maxWidth, int maxHeight) { if (currentChatHead != null) { manager.detachView(currentChatHead, getArrowLayout()); } hideView(); manager.hideOverlayView(true); positions.clear(); isActive = false; } @Override public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVelocity, Spring activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging) { if (xVelocity == 0 && yVelocity == 0) { // 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 xVelocity = 1; yVelocity = 1; } activeHorizontalSpring.setVelocity(xVelocity); activeVerticalSpring.setVelocity(yVelocity); if (wasDragging) { return true; } else { if (activeChatHead != currentChatHead) { boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { selectTab(activeChatHead); return true; } } boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { deactivate(); } return handled; } } private void selectTab(final ChatHead<T> activeChatHead) { if (currentChatHead != activeChatHead) { detach(currentChatHead); currentChatHead = activeChatHead; } pointTo(activeChatHead); showOrHideView(activeChatHead); } private void detach(ChatHead chatHead) { manager.detachView(chatHead, getArrowLayout()); } private void positionToOriginal(ChatHead activeChatHead, Spring activeHorizontalSpring, Spring activeVerticalSpring) { if (activeChatHead.isSticky()) { Point point = positions.get(activeChatHead); if (point != null) { double distanceFromOriginal = Math.hypot(point.x - activeHorizontalSpring.getCurrentValue(), point.y - activeVerticalSpring.getCurrentValue()); if (distanceFromOriginal > MAX_DISTANCE_FROM_ORIGINAL) { deactivate(); return; } } } if (activeChatHead.getState() == ChatHead.State.FREE) { Point point = positions.get(activeChatHead); if (point != null) { activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); activeHorizontalSpring.setVelocity(0); activeHorizontalSpring.setEndValue(point.x); activeVerticalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); activeVerticalSpring.setVelocity(0); activeVerticalSpring.setEndValue(point.y); } } } @Override public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxWidth, int maxHeight, Spring spring, Spring activeHorizontalSpring, Spring activeVerticalSpring, int totalVelocity) { /** Bounds Check **/ if (spring == activeHorizontalSpring && !isDragging) { double xPosition = activeHorizontalSpring.getCurrentValue(); if (xPosition + manager.getConfig().getHeadWidth() > maxWidth && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { positionToOriginal(activeChatHead, activeHorizontalSpring, activeVerticalSpring); } if (xPosition < 0 && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { positionToOriginal(activeChatHead, activeHorizontalSpring, activeVerticalSpring); } } else if (spring == activeVerticalSpring && !isDragging) { double yPosition = activeVerticalSpring.getCurrentValue(); if (yPosition + manager.getConfig().getHeadHeight() > maxHeight && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { positionToOriginal(activeChatHead, activeHorizontalSpring, activeVerticalSpring); } if (yPosition < 0 && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { positionToOriginal(activeChatHead, activeHorizontalSpring, activeVerticalSpring); } } /** position it back **/ if (!isDragging && totalVelocity < MIN_VELOCITY_TO_POSITION_BACK && activeHorizontalSpring.getSpringConfig() == SpringConfigsHolder.DRAGGING) { positionToOriginal(activeChatHead, activeHorizontalSpring, activeVerticalSpring); } if (activeChatHead == currentChatHead) showOrHideView(activeChatHead); if (!isDragging) { /** 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 && !activeChatHead.isSticky()) { 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); } } } private void showOrHideView(ChatHead activeChatHead) { Point point = positions.get(activeChatHead); if (point != null) { double dx = activeChatHead.getHorizontalSpring().getCurrentValue() - point.x; double dy = activeChatHead.getVerticalSpring().getCurrentValue() - point.y; double distanceFromOriginal = Math.hypot(dx, dy); if (distanceFromOriginal < maxDistanceFromOriginal) { showView(activeChatHead, dx, dy, distanceFromOriginal); } else { hideView(); } } } private UpArrowLayout getArrowLayout() { if (arrowLayout == null) { arrowLayout = manager.getArrowLayout(); } return arrowLayout; } private boolean isViewHidden() { UpArrowLayout arrowLayout = getArrowLayout(); if (arrowLayout != null) { return arrowLayout.getVisibility() == View.GONE; } return true; } private void hideView() { UpArrowLayout arrowLayout = getArrowLayout(); arrowLayout.setVisibility(View.GONE); } private void showView(ChatHead activeChatHead, double dx, double dy, double distanceFromOriginal) { UpArrowLayout arrowLayout = getArrowLayout(); arrowLayout.setVisibility(View.VISIBLE); arrowLayout.setTranslationX((float) dx); arrowLayout.setTranslationY((float) dy); arrowLayout.setAlpha(1f - ((float) distanceFromOriginal / (float) maxDistanceFromOriginal)); } public static void sendViewToBack(final View child) { final ViewGroup parent = (ViewGroup) child.getParent(); if (null != parent && parent.indexOfChild(child) != 0) { parent.removeView(child); parent.addView(child, 0); } } private void pointTo(ChatHead<T> activeChatHead) { UpArrowLayout arrowLayout = getArrowLayout(); getArrowLayout().removeAllViews(); manager.attachView(activeChatHead, arrowLayout); sendViewToBack(manager.getOverlayView()); Point point = positions.get(activeChatHead); if (point != null) { int padding = manager.getConfig().getHeadVerticalSpacing(maxWidth, maxHeight); arrowLayout.pointTo(point.x + manager.getConfig().getHeadWidth() / 2, point.y + manager.getConfig().getHeadHeight() + padding); } } @Override public void onChatHeadAdded(final ChatHead chatHead, final boolean animated) { //we post so that chat head measurement is done Spring spring = chatHead.getHorizontalSpring(); spring.setCurrentValue(maxWidth).setAtRest(); spring = chatHead.getVerticalSpring(); spring.setCurrentValue(topPadding).setAtRest(); onActivate(manager, getBundleWithHero(), maxWidth, maxHeight, animated); } @Override public void onChatHeadRemoved(ChatHead removed) { manager.detachView(removed, getArrowLayout()); manager.removeView(removed, getArrowLayout()); positions.remove(removed); boolean isEmpty = false; if (currentChatHead == removed) { ChatHead nextBestChatHead = getNextBestChatHead(); if (nextBestChatHead != null) { isEmpty = false; selectTab(nextBestChatHead); } else { isEmpty = true; } } if (!isEmpty) { onActivate(manager, getBundleWithHero(), maxWidth, maxHeight, true); } else { deactivate(); } } @Override public void onCapture(ChatHeadManager container, ChatHead activeChatHead) { if (!activeChatHead.isSticky()) { container.removeChatHead(activeChatHead.getKey(), true); } } @Override public void selectChatHead(final ChatHead chatHead) { selectTab(chatHead); } private ChatHead getNextBestChatHead() { ChatHead nextBestChatHead = null; for (ChatHead head : manager.getChatHeads()) { if (nextBestChatHead == null) { nextBestChatHead = head; } else if (head.getUnreadCount() >= nextBestChatHead.getUnreadCount()) { nextBestChatHead = head; } } return nextBestChatHead; } private Bundle getBundleWithHero() { Bundle bundle = extras; if (bundle == null) { bundle = new Bundle(); } bundle.putInt(MinimizedArrangement.BUNDLE_HERO_INDEX_KEY, getHeroIndex()); return bundle; } private void deactivate() { manager.setArrangement(MinimizedArrangement.class, getBundleWithHero()); hideView(); } /** * @return the index of the selected chat head a.k.a the hero */ @Override public Integer getHeroIndex() { int heroIndex = 0; List<ChatHead<T>> chatHeads = manager.getChatHeads(); int i = 0; for (ChatHead<T> chatHead : chatHeads) { if (currentChatHead == chatHead) { heroIndex = i; } i++; } return heroIndex; } @Override public void onConfigChanged(ChatHeadConfig newConfig) { } @Override public Bundle getRetainBundle() { return getBundleWithHero(); } @Override public boolean canDrag(ChatHead chatHead) { if (chatHead.isSticky()) return false; return true; } @Override public void removeOldestChatHead() { for (ChatHead<T> chatHead : manager.getChatHeads()) { //we dont remove sticky chat heads as well as the currently selected chat head if (!chatHead.isSticky() && chatHead != currentChatHead) { manager.removeChatHead(chatHead.getKey(), false); break; } } } @Override public void bringToFront(final ChatHead chatHead) { //nothing to do, everything is in front. selectChatHead(chatHead); } @Override public void onReloadFragment(ChatHead chatHead) { if (currentChatHead != null && chatHead == currentChatHead) { manager.attachView(chatHead, getArrowLayout()); } } @Override public boolean shouldShowCloseButton(ChatHead chatHead) { if (chatHead.isSticky()) return false; return true; } }