package com.flipkart.chatheads.ui.container; import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; import com.facebook.rebound.SpringConfigRegistry; import com.facebook.rebound.SpringSystem; import com.flipkart.chatheads.R; import com.flipkart.chatheads.ui.ChatHead; import com.flipkart.chatheads.ui.ChatHeadArrangement; import com.flipkart.chatheads.ui.ChatHeadCloseButton; import com.flipkart.chatheads.ui.ChatHeadConfig; import com.flipkart.chatheads.ui.ChatHeadDefaultConfig; import com.flipkart.chatheads.ui.ChatHeadListener; import com.flipkart.chatheads.ui.ChatHeadManager; import com.flipkart.chatheads.ui.ChatHeadOverlayView; import com.flipkart.chatheads.ui.ChatHeadViewAdapter; import com.flipkart.chatheads.ui.MaximizedArrangement; import com.flipkart.chatheads.ui.MinimizedArrangement; import com.flipkart.chatheads.ui.SpringConfigsHolder; import com.flipkart.chatheads.ui.UpArrowLayout; import com.flipkart.chatheads.ui.ChatHeadContainer; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class DefaultChatHeadManager<T extends Serializable> implements ChatHeadCloseButton.CloseButtonListener, ChatHeadManager<T> { private static final int OVERLAY_TRANSITION_DURATION = 200; private final Map<Class<? extends ChatHeadArrangement>, ChatHeadArrangement> arrangements = new HashMap<>(3); private final Context context; private final ChatHeadContainer chatHeadContainer; private List<ChatHead<T>> chatHeads; private int maxWidth; private int maxHeight; private ChatHeadCloseButton closeButton; private ChatHeadArrangement activeArrangement; private ChatHeadViewAdapter<T> viewAdapter; private ChatHeadOverlayView overlayView; private OnItemSelectedListener<T> itemSelectedListener; private boolean overlayVisible; private ImageView closeButtonShadow; private SpringSystem springSystem; private FragmentManager fragmentManager; private Fragment currentFragment; private ChatHeadConfig config; private ChatHeadListener listener; private Bundle activeArrangementBundle; private ArrangementChangeRequest requestedArrangement; private DisplayMetrics displayMetrics; private UpArrowLayout arrowLayout; public DefaultChatHeadManager(Context context, ChatHeadContainer chatHeadContainer) { this.context = context; this.chatHeadContainer = chatHeadContainer; this.displayMetrics = chatHeadContainer.getDisplayMetrics(); init(context, new ChatHeadDefaultConfig(context)); } public ChatHeadContainer getChatHeadContainer() { return chatHeadContainer; } @Override public DisplayMetrics getDisplayMetrics() { return displayMetrics; } @Override public ChatHeadListener getListener() { return listener; } @Override public void setListener(ChatHeadListener listener) { this.listener = listener; } @Override public List<ChatHead<T>> getChatHeads() { return chatHeads; } @Override public ChatHeadViewAdapter getViewAdapter() { return viewAdapter; } @Override public void setViewAdapter(ChatHeadViewAdapter chatHeadViewAdapter) { this.viewAdapter = chatHeadViewAdapter; } @Override public ChatHeadCloseButton getCloseButton() { return closeButton; } @Override public int getMaxWidth() { return maxWidth; } @Override public int getMaxHeight() { return maxHeight; } @Override public Context getContext() { return context; } @Override public Class<? extends ChatHeadArrangement> getArrangementType() { if (activeArrangement != null) { return activeArrangement.getClass(); } else if (requestedArrangement != null) { return requestedArrangement.getArrangement(); } return null; } @Override public ChatHeadArrangement getActiveArrangement() { if (activeArrangement != null) { return activeArrangement; } return null; } @Override public void selectChatHead(ChatHead chatHead) { if (activeArrangement != null) activeArrangement.selectChatHead(chatHead); } @Override public void selectChatHead(T key) { ChatHead chatHead = findChatHeadByKey(key); if (chatHead != null) { selectChatHead(chatHead); } } @Override public void onMeasure(int height, int width) { boolean needsLayout = false; if (height != maxHeight && width != maxWidth) { needsLayout = true; // both changed, must be screen rotation. } maxHeight = height; maxWidth = width; int closeButtonCenterX = (int) ((float) width * 0.5f); int closeButtonCenterY = (int) ((float) height * 0.9f); closeButton.onParentHeightRefreshed(); closeButton.setCenter(closeButtonCenterX, closeButtonCenterY); if (maxHeight > 0 && maxWidth > 0) { if (requestedArrangement != null) { setArrangementImpl(requestedArrangement); requestedArrangement = null; } else { if (needsLayout) { // this means height changed and we need to redraw. setArrangementImpl(new ArrangementChangeRequest(activeArrangement.getClass(), null, false)); } } } } // @Override // public boolean dispatchTouchEvent(MotionEvent ev) { // if (activeArrangement != null) { // activeArrangement.handleRawTouchEvent(ev); // } // return super.dispatchTouchEvent(ev); // } @Override public ChatHead<T> addChatHead(T key, boolean isSticky, boolean animated) { ChatHead<T> chatHead = findChatHeadByKey(key); if (chatHead == null) { chatHead = new ChatHead<T>(this, springSystem, getContext(), isSticky); chatHead.setKey(key); chatHeads.add(chatHead); ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getHeadWidth(), getConfig().getHeadHeight(), Gravity.START | Gravity.TOP, 0); chatHeadContainer.addView(chatHead, layoutParams); if (chatHeads.size() > config.getMaxChatHeads(maxWidth, maxHeight) && activeArrangement != null) { activeArrangement.removeOldestChatHead(); } reloadDrawable(key); if (activeArrangement != null) activeArrangement.onChatHeadAdded(chatHead, animated); else { chatHead.getHorizontalSpring().setCurrentValue(-100); chatHead.getVerticalSpring().setCurrentValue(-100); } if (listener != null) { listener.onChatHeadAdded(key); } closeButtonShadow.bringToFront(); } return chatHead; } @Override public ChatHead<T> findChatHeadByKey(T key) { for (ChatHead<T> chatHead : chatHeads) { if (chatHead.getKey().equals(key)) return chatHead; } return null; } @Override public void reloadDrawable(T key) { Drawable chatHeadDrawable = viewAdapter.getChatHeadDrawable(key); if (chatHeadDrawable != null) { findChatHeadByKey(key).setImageDrawable(viewAdapter.getChatHeadDrawable(key)); } } @Override public void removeAllChatHeads(boolean userTriggered) { for (Iterator<ChatHead<T>> iterator = chatHeads.iterator(); iterator.hasNext(); ) { ChatHead<T> chatHead = iterator.next(); iterator.remove(); onChatHeadRemoved(chatHead, userTriggered); } } @Override public boolean removeChatHead(T key, boolean userTriggered) { ChatHead chatHead = findChatHeadByKey(key); if (chatHead != null) { chatHeads.remove(chatHead); onChatHeadRemoved(chatHead, userTriggered); return true; } return false; } private void onChatHeadRemoved(ChatHead chatHead, boolean userTriggered) { if (chatHead != null && chatHead.getParent() != null) { chatHead.onRemove(); chatHeadContainer.removeView(chatHead); if (activeArrangement != null) activeArrangement.onChatHeadRemoved(chatHead); if (listener != null) { listener.onChatHeadRemoved(chatHead.getKey(), userTriggered); } } } @Override public ChatHeadOverlayView getOverlayView() { return overlayView; } private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { chatHeadContainer.onInitialized(this); DisplayMetrics metrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(metrics); this.displayMetrics = metrics; this.config = chatHeadDefaultConfig; //TODO : needs cleanup chatHeads = new ArrayList<>(5); arrowLayout = new UpArrowLayout(context); arrowLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); chatHeadContainer.addView(arrowLayout, arrowLayout.getLayoutParams()); arrowLayout.setVisibility(View.GONE); springSystem = SpringSystem.create(); closeButton = new ChatHeadCloseButton(context, this, maxHeight, maxWidth); ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(chatHeadDefaultConfig.getCloseButtonHeight(), chatHeadDefaultConfig.getCloseButtonWidth(), Gravity.TOP | Gravity.START, 0); closeButton.setListener(this); chatHeadContainer.addView(closeButton, layoutParams); closeButtonShadow = new ImageView(getContext()); ViewGroup.LayoutParams shadowLayoutParams = chatHeadContainer.createLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.BOTTOM, 0); closeButtonShadow.setImageResource(R.drawable.dismiss_shadow); closeButtonShadow.setVisibility(View.GONE); chatHeadContainer.addView(closeButtonShadow, shadowLayoutParams); arrangements.put(MinimizedArrangement.class, new MinimizedArrangement(this)); arrangements.put(MaximizedArrangement.class, new MaximizedArrangement<T>(this)); setupOverlay(context); setConfig(chatHeadDefaultConfig); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.DRAGGING, "dragging mode"); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.NOT_DRAGGING, "not dragging mode"); } private void setupOverlay(Context context) { overlayView = new ChatHeadOverlayView(context); overlayView.setBackgroundResource(R.drawable.overlay_transition); ViewGroup.LayoutParams layoutParams = getChatHeadContainer().createLayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, Gravity.NO_GRAVITY, 0); getChatHeadContainer().addView(overlayView, layoutParams); } public double getDistanceCloseButtonFromHead(float touchX, float touchY) { if (closeButton.isDisappeared()) { return Double.MAX_VALUE; } else { int left = closeButton.getLeft(); int top = closeButton.getTop(); double xDiff = touchX - left - getChatHeadContainer().getViewX(closeButton) - closeButton.getMeasuredWidth() / 2; double yDiff = touchY - top - getChatHeadContainer().getViewY(closeButton) - closeButton.getMeasuredHeight() / 2; double distance = Math.hypot(xDiff, yDiff); return distance; } } @Override public UpArrowLayout getArrowLayout() { return arrowLayout; } @Override public void captureChatHeads(ChatHead causingChatHead) { activeArrangement.onCapture(this, causingChatHead); } @Override public ChatHeadArrangement getArrangement(Class<? extends ChatHeadArrangement> arrangementType) { return arrangements.get(arrangementType); } @Override public void setArrangement(final Class<? extends ChatHeadArrangement> arrangement, Bundle extras) { setArrangement(arrangement, extras, true); } @Override public void setArrangement(final Class<? extends ChatHeadArrangement> arrangement, Bundle extras, boolean animated) { this.requestedArrangement = new ArrangementChangeRequest(arrangement, extras, animated); chatHeadContainer.requestLayout(); } /** * Should only be called after onMeasure * * @param requestedArrangementParam */ private void setArrangementImpl(ArrangementChangeRequest requestedArrangementParam) { boolean hasChanged = false; ChatHeadArrangement requestedArrangement = arrangements.get(requestedArrangementParam.getArrangement()); ChatHeadArrangement oldArrangement = null; ChatHeadArrangement newArrangement = requestedArrangement; Bundle extras = requestedArrangementParam.getExtras(); if (activeArrangement != requestedArrangement) hasChanged = true; if (extras == null) extras = new Bundle(); if (activeArrangement != null) { extras.putAll(activeArrangement.getRetainBundle()); activeArrangement.onDeactivate(maxWidth, maxHeight); oldArrangement = activeArrangement; } activeArrangement = requestedArrangement; activeArrangementBundle = extras; requestedArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangementParam.isAnimated()); if (hasChanged) { chatHeadContainer.onArrangementChanged(oldArrangement, newArrangement); if (listener != null) listener.onChatHeadArrangementChanged(oldArrangement, newArrangement); } } @Override public void hideOverlayView(boolean animated) { if (overlayVisible) { TransitionDrawable drawable = (TransitionDrawable) overlayView.getBackground(); int duration = OVERLAY_TRANSITION_DURATION; if (!animated) duration = 0; drawable.reverseTransition(duration); overlayView.setClickable(false); overlayVisible = false; } } @Override public void showOverlayView(boolean animated) { if (!overlayVisible) { TransitionDrawable drawable = (TransitionDrawable) overlayView.getBackground(); int duration = OVERLAY_TRANSITION_DURATION; if (!animated) duration = 0; drawable.startTransition(duration); overlayView.setClickable(true); overlayVisible = true; } } @Override public int[] getChatHeadCoordsForCloseButton(ChatHead chatHead) { int[] coords = new int[2]; int x = (int) (closeButton.getLeft() + closeButton.getEndValueX() + closeButton.getMeasuredWidth() / 2 - chatHead.getMeasuredWidth() / 2); int y = (int) (closeButton.getTop() + closeButton.getEndValueY() + closeButton.getMeasuredHeight() / 2 - chatHead.getMeasuredHeight() / 2); coords[0] = x; coords[1] = y; return coords; } @Override public void setOnItemSelectedListener(OnItemSelectedListener<T> onItemSelectedListener) { this.itemSelectedListener = onItemSelectedListener; } @Override public boolean onItemSelected(ChatHead<T> chatHead) { return itemSelectedListener != null && itemSelectedListener.onChatHeadSelected(chatHead.getKey(), chatHead); } @Override public void onItemRollOver(ChatHead<T> chatHead) { if (itemSelectedListener != null) itemSelectedListener.onChatHeadRollOver(chatHead.getKey(), chatHead); } @Override public void onItemRollOut(ChatHead<T> chatHead) { if (itemSelectedListener != null) itemSelectedListener.onChatHeadRollOut(chatHead.getKey(), chatHead); } @Override public void bringToFront(ChatHead chatHead) { if (activeArrangement != null) { activeArrangement.bringToFront(chatHead); } } @Override public void onCloseButtonAppear() { if (!getConfig().isCloseButtonHidden()) { closeButtonShadow.setVisibility(View.VISIBLE); } } @Override public void onCloseButtonDisappear() { closeButtonShadow.setVisibility(View.GONE); } @Override public void recreateView(T key) { detachView(findChatHeadByKey(key),getArrowLayout()); removeView(findChatHeadByKey(key), getArrowLayout()); if (activeArrangement != null) { activeArrangement.onReloadFragment(findChatHeadByKey(key)); } } @Override public SpringSystem getSpringSystem() { return springSystem; } @Override public View attachView(ChatHead<T> activeChatHead, ViewGroup parent) { View view = viewAdapter.attachView(activeChatHead.getKey(), activeChatHead, parent); return view; } @Override public void removeView(ChatHead<T> chatHead, ViewGroup parent) { viewAdapter.removeView(chatHead.getKey(), chatHead, parent); } @Override public void detachView(ChatHead<T> chatHead, ViewGroup parent) { viewAdapter.detachView(chatHead.getKey(), chatHead, parent); } @Override public ChatHeadConfig getConfig() { return config; } @Override public void setConfig(ChatHeadConfig config) { this.config = config; if (closeButton != null) { // LayoutParams params = (LayoutParams) closeButton.getLayoutParams(); // params.width = config.getCloseButtonWidth(); // params.height = config.getCloseButtonHeight(); // params.bottomMargin = config.getCloseButtonBottomMargin(); // closeButton.setLayoutParams(params); if (config.isCloseButtonHidden()) { closeButton.setVisibility(View.GONE); closeButtonShadow.setVisibility(View.GONE); } else { closeButton.setVisibility(View.VISIBLE); closeButtonShadow.setVisibility(View.VISIBLE); } } for (Map.Entry<Class<? extends ChatHeadArrangement>, ChatHeadArrangement> arrangementEntry : arrangements.entrySet()) { arrangementEntry.getValue().onConfigChanged(config); } } @Override public Parcelable onSaveInstanceState(Parcelable superState) { SavedState savedState = new SavedState(superState); if (activeArrangement != null) { savedState.setActiveArrangement(activeArrangement.getClass()); savedState.setActiveArrangementBundle(activeArrangement.getRetainBundle()); } LinkedHashMap<T, Boolean> chatHeadState = new LinkedHashMap<>(); for (ChatHead<T> chatHead : chatHeads) { T key = chatHead.getKey(); boolean sticky = chatHead.isSticky(); chatHeadState.put(key, sticky); } savedState.setChatHeads(chatHeadState); return savedState; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof SavedState) { SavedState savedState = (SavedState) state; final Class activeArrangementClass = savedState.getActiveArrangement(); final Bundle activeArrangementBundle = savedState.getActiveArrangementBundle(); final Map<? extends Serializable, Boolean> chatHeads = savedState.getChatHeads(); for (Map.Entry<? extends Serializable, Boolean> entry : chatHeads.entrySet()) { T key = (T) entry.getKey(); Boolean sticky = entry.getValue(); addChatHead(key, sticky, false); } if (activeArrangementClass != null) { setArrangement(activeArrangementClass, activeArrangementBundle, false); } //view.onRestoreInstanceState(savedState.getSuperState()); } else { //view.onRestoreInstanceState(state); } } @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { if (closeButton != null) { closeButton.onParentHeightRefreshed(); } } static class SavedState extends View.BaseSavedState { public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel source) { return new SavedState(source); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; private Class<? extends ChatHeadArrangement> activeArrangement; private Bundle activeArrangementBundle; private LinkedHashMap<? extends Serializable, Boolean> chatHeads; public SavedState(Parcel source) { super(source); activeArrangement = (Class<? extends ChatHeadArrangement>) source.readSerializable(); activeArrangementBundle = source.readBundle(); chatHeads = (LinkedHashMap<? extends Serializable, Boolean>) source.readSerializable(); } public SavedState(Parcelable superState) { super(superState); } public Class<? extends ChatHeadArrangement> getActiveArrangement() { return activeArrangement; } public void setActiveArrangement(Class<? extends ChatHeadArrangement> activeArrangement) { this.activeArrangement = activeArrangement; } public Bundle getActiveArrangementBundle() { return activeArrangementBundle; } public void setActiveArrangementBundle(Bundle activeArrangementBundle) { this.activeArrangementBundle = activeArrangementBundle; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeSerializable(activeArrangement); dest.writeBundle(activeArrangementBundle); dest.writeSerializable(chatHeads); } public Map<? extends Serializable, Boolean> getChatHeads() { return chatHeads; } public void setChatHeads(LinkedHashMap<? extends Serializable, Boolean> chatHeads) { this.chatHeads = chatHeads; } } private class ArrangementChangeRequest { private final Bundle extras; private final Class<? extends ChatHeadArrangement> arrangement; private final boolean animated; public ArrangementChangeRequest(Class<? extends ChatHeadArrangement> arrangement, Bundle extras, boolean animated) { this.arrangement = arrangement; this.extras = extras; this.animated = animated; } public Bundle getExtras() { return extras; } public Class<? extends ChatHeadArrangement> getArrangement() { return arrangement; } public boolean isAnimated() { return animated; } } }