From fb488f6efc6f372b55391f1b6f0fd3fe07a0853f Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 8 Nov 2016 15:52:18 +0530 Subject: [PATCH 01/14] got basic chat heads working in a service. Maximized arrangement still needs work. --- README.md | 2 +- demo/build.gradle | 3 +- demo/src/main/AndroidManifest.xml | 4 +- .../springyheads/demo/ChatHeadService.java | 106 +++ .../demo/CustomChatHeadConfig.java | 2 +- .../springyheads/demo/FloatingActivity.java | 18 + .../springyheads/demo/MainActivity.java | 247 ------ .../com/flipkart/chatheads/ui/ChatHead.java | 71 +- .../chatheads/ui/ChatHeadArrangement.java | 9 +- .../chatheads/ui/ChatHeadCloseButton.java | 49 +- .../chatheads/ui/ChatHeadContainer.java | 709 +---------------- .../chatheads/ui/ChatHeadManager.java | 172 +++++ .../chatheads/ui/CircularArrangement.java | 62 +- .../chatheads/ui/FrameChatHeadContainer.java | 91 +++ .../chatheads/ui/MaximizedArrangement.java | 75 +- .../chatheads/ui/MinimizedArrangement.java | 68 +- .../ui/container/DefaultChatHeadManager.java | 714 ++++++++++++++++++ .../ui/container/WindowManagerContainer.java | 152 ++++ library/src/main/res/layout/arrow_layout.xml | 6 - 19 files changed, 1454 insertions(+), 1106 deletions(-) create mode 100644 demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java create mode 100644 demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java delete mode 100755 demo/src/main/java/com/flipkart/springyheads/demo/MainActivity.java mode change 100755 => 100644 library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java create mode 100644 library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java create mode 100644 library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java create mode 100755 library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java create mode 100644 library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java delete mode 100644 library/src/main/res/layout/arrow_layout.xml diff --git a/README.md b/README.md index c5106da..203416c 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ public class CustomChatHeadConfig extends ChatHeadDefaultConfig { } } ``` -Once this config class is defined, you can set it to the container +Once this config class is defined, you can set it to the manager ```java chatContainer.setConfig(new CustomChatHeadConfig(this, 0, 100); ``` diff --git a/demo/build.gradle b/demo/build.gradle index 0d97a19..6adb63d 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -21,5 +21,6 @@ android { dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') - compile 'com.flipkart.springyheads:library:0.9.6' + // compile 'com.flipkart.springyheads:library:0.9.6' + compile project(':library') } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index e8138e2..f6d6713 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ android:label="@string/app_name" android:theme="@style/AppTheme" > @@ -17,6 +17,8 @@ + + diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java new file mode 100644 index 0000000..688a797 --- /dev/null +++ b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java @@ -0,0 +1,106 @@ +package com.flipkart.springyheads.demo; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.NotificationCompat; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; + +import com.flipkart.chatheads.ui.ChatHead; +import com.flipkart.chatheads.ui.ChatHeadViewAdapter; +import com.flipkart.chatheads.ui.FrameChatHeadContainer; +import com.flipkart.chatheads.ui.MinimizedArrangement; +import com.flipkart.chatheads.ui.ChatHeadContainer; +import com.flipkart.chatheads.ui.container.DefaultChatHeadManager; +import com.flipkart.chatheads.ui.container.WindowManagerContainer; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + +public class ChatHeadService extends Service { + + private DefaultChatHeadManager chatContainer; + + + @Override + public IBinder onBind(Intent intent) { + // Not used + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + final WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + ChatHeadContainer chatHeadContainer = new WindowManagerContainer(this); + + chatContainer = new DefaultChatHeadManager(this, chatHeadContainer); + + chatContainer.setViewAdapter(new ChatHeadViewAdapter() { + @Override + public FragmentManager getFragmentManager() { + return null; + } + + @Override + public Fragment instantiateFragment(Object key, ChatHead chatHead) { + return TestFragment.newInstance(0); + } + + @Override + public Drawable getChatHeadDrawable(Object key) { + return getResources().getDrawable(R.drawable.head); + } + + @Override + public Drawable getPointerDrawable() { + return getResources().getDrawable(R.drawable.circular_ring); + } + + @Override + public View getTitleView(Object key, ChatHead chatHead) { + return null; + } + }); + + + addChatHead(); + addChatHead(); + addChatHead(); + addChatHead(); + chatContainer.setArrangement(MinimizedArrangement.class, null); + chatContainer.onMeasure(); + + moveToForeground(); + + } + + private void moveToForeground() { + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification_template_icon_bg) + .setContentText("Chat heads is active") + .build(); + + startForeground(1, notification); + } + + private void addChatHead() { + chatContainer.addChatHead("head" + Math.random(), false, true); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} \ No newline at end of file diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/CustomChatHeadConfig.java b/demo/src/main/java/com/flipkart/springyheads/demo/CustomChatHeadConfig.java index 883c0be..1f39731 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/CustomChatHeadConfig.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/CustomChatHeadConfig.java @@ -3,7 +3,7 @@ import android.content.Context; import android.graphics.Point; -import com.flipkart.chatheads.reboundextensions.ChatHeadUtils; +import com.flipkart.chatheads.ChatHeadUtils; import com.flipkart.chatheads.ui.ChatHeadDefaultConfig; /** diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java b/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java new file mode 100644 index 0000000..cef174f --- /dev/null +++ b/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java @@ -0,0 +1,18 @@ +package com.flipkart.springyheads.demo; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * Created by kiran.kumar on 06/02/16. + */ +public class FloatingActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + startService(new Intent(this, ChatHeadService.class)); + finish(); + } +} diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/MainActivity.java b/demo/src/main/java/com/flipkart/springyheads/demo/MainActivity.java deleted file mode 100755 index e2d4cfe..0000000 --- a/demo/src/main/java/com/flipkart/springyheads/demo/MainActivity.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.flipkart.springyheads.demo; - -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.SharedPreferences; -import android.graphics.Point; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Display; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.OvershootInterpolator; -import android.widget.TextView; - -import com.flipkart.chatheads.ui.ChatHead; -import com.flipkart.chatheads.ui.ChatHeadArrangement; -import com.flipkart.chatheads.ui.ChatHeadContainer; -import com.flipkart.chatheads.ui.ChatHeadListener; -import com.flipkart.chatheads.ui.ChatHeadViewAdapter; -import com.flipkart.chatheads.ui.CircularArrangement; -import com.flipkart.chatheads.ui.MaximizedArrangement; -import com.flipkart.chatheads.ui.MinimizedArrangement; -import com.flipkart.springyheads.demo.R; - -import java.util.List; - - -public class MainActivity extends ActionBarActivity { - - - private View circularClickArea; - private SharedPreferences chatHeadPreferences; - private ChatHeadContainer chatContainer; - private TextView chatHeadLabel; - private int id = 0; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.add_head: - addChatHead(); - break; - case R.id.toggle: - if (chatContainer.getArrangementType() == MinimizedArrangement.class) { - chatContainer.setArrangement(MaximizedArrangement.class, new Bundle()); - } else { - chatContainer.setArrangement(MinimizedArrangement.class, new Bundle()); - } - break; - case R.id.remove_head: - List chatHeads = chatContainer.getChatHeads(); - if (chatHeads.size() > 0) { - double rand = Math.random() * (float) chatHeads.size(); - ChatHead chatHead = (ChatHead) chatHeads.get((int) rand); - chatContainer.removeChatHead(chatHead.getKey(),false); - } - break; - case R.id.remove_all_heads: - chatContainer.removeAllChatHeads(false); - break; - case R.id.select_random: - chatHeads = chatContainer.getChatHeads(); - if (chatHeads.size() > 0) { - double rand = Math.random() * (float) chatHeads.size(); - ChatHead chatHead = (ChatHead) chatHeads.get((int) rand); - chatContainer.bringToFront(chatHead); - } - break; - } - return true; - } - - private void addChatHead() { - chatContainer.addChatHead("head" + Math.random(), false, true); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - circularClickArea = findViewById(R.id.circular_click_area); - chatHeadPreferences = getSharedPreferences("chat", MODE_PRIVATE); - chatHeadLabel = (TextView) findViewById(R.id.chat_head_label); - chatContainer = (ChatHeadContainer) findViewById(R.id.chat_container); - chatContainer.setViewAdapter(new ChatHeadViewAdapter() { - @Override - public FragmentManager getFragmentManager() { - return getSupportFragmentManager(); - } - - @Override - public Fragment instantiateFragment(Object key, ChatHead chatHead) { - id++; - return TestFragment.newInstance(id); - } - - @Override - public Drawable getChatHeadDrawable(Object key) { - return getResources().getDrawable(R.drawable.head); - } - - @Override - public Drawable getPointerDrawable() { - return getResources().getDrawable(R.drawable.circular_ring); - } - - @Override - public View getTitleView(Object key, ChatHead chatHead) { - return null; - } - }); - chatContainer.setOnItemSelectedListener(new ChatHeadContainer.OnItemSelectedListener() { - @Override - public boolean onChatHeadSelected(Object key, ChatHead chatHead) { - return false; - } - - @Override - public void onChatHeadRollOver(Object key, final ChatHead chatHead) { - chatHeadLabel.setTranslationX(chatHead.getTranslationX() + chatHead.getMeasuredWidth() / 2 - chatHeadLabel.getMeasuredWidth() / 2); - float yStart = chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2 - chatHeadLabel.getMeasuredHeight(); - float yEnd = chatHead.getTranslationY() - chatHeadLabel.getMeasuredHeight(); - ObjectAnimator objectAnimatorTranslationY = ObjectAnimator.ofFloat(chatHeadLabel, "translationY", yStart, yEnd); - ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Float animatedValue = (Float) animation.getAnimatedValue(); - chatHeadLabel.setScaleX(animatedValue); - chatHeadLabel.setScaleY(animatedValue); - } - }); - valueAnimator.setDuration(500); - valueAnimator.setInterpolator(new OvershootInterpolator()); - valueAnimator.start(); - objectAnimatorTranslationY.setDuration(500); - objectAnimatorTranslationY.setInterpolator(new OvershootInterpolator()); - objectAnimatorTranslationY.start(); - //chatHeadLabel.setVisibility(View.VISIBLE); - } - - @Override - public void onChatHeadRollOut(Object key, ChatHead chatHead) { - chatHeadLabel.setVisibility(View.INVISIBLE); - } - }); - chatContainer.setListener(new ChatHeadListener() { - @Override - public void onChatHeadAdded(Object key) { - } - - @Override - public void onChatHeadRemoved(Object key, boolean userTriggered) { - } - - @Override - public void onChatHeadArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArrangement newArrangement) { - setTitle(newArrangement.getClass().getSimpleName()); - } - - @Override - public void onChatHeadAnimateStart(ChatHead chatHead) { - - } - - @Override - public void onChatHeadAnimateEnd(ChatHead chatHead) { - - } - }); - chatContainer.setConfig(new CustomChatHeadConfig(this, getInitialX(), getInitialY())); - if (savedInstanceState == null) { - chatContainer.setArrangement(MinimizedArrangement.class, null); - - } - circularClickArea.setOnTouchListener(new View.OnTouchListener() { - - Bundle bundle = new Bundle(); - Runnable longPressCallback = new Runnable() { - @Override - public void run() { - chatContainer.setArrangement(CircularArrangement.class, bundle); - } - }; - - @Override - public boolean onTouch(View v, MotionEvent event) { - chatContainer.dispatchTouchEvent(event); - if (event.getAction() == MotionEvent.ACTION_DOWN) { - circularClickArea.removeCallbacks(longPressCallback); - bundle.putInt(CircularArrangement.BUNDLE_KEY_X, (int) event.getX()); - bundle.putInt(CircularArrangement.BUNDLE_KEY_Y, (int) event.getY()); - circularClickArea.postDelayed(longPressCallback, 1000); - } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { - circularClickArea.removeCallbacks(longPressCallback); - } - return true; - } - }); - chatContainer.setArrangement(MinimizedArrangement.class, new Bundle()); - addChatHead(); - addChatHead(); - } - - private int getInitialX() { - Display display = getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int width = size.x; - int height = size.y; - float defaultChatHeadXPosition = width; - return chatHeadPreferences.getInt("initialX", (int) defaultChatHeadXPosition); - } - - private int getInitialY() { - Display display = getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int width = size.x; - int height = size.y; - float defaultChatHeadYPosition = height * 0.50f; - return chatHeadPreferences.getInt("initialY", (int) defaultChatHeadYPosition); - } - - @Override - protected void onPause() { - super.onPause(); - MinimizedArrangement arrangement = (MinimizedArrangement) chatContainer.getArrangement(MinimizedArrangement.class); - Point idleStatePosition = arrangement.getIdleStatePosition(); - Log.v("idle_position", idleStatePosition.toString()); - chatHeadPreferences.edit().putInt("initialX", idleStatePosition.x).putInt("initialY", idleStatePosition.y).apply(); - } -} diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java index d1a88b3..f57c2c6 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java @@ -10,7 +10,6 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; -import android.view.ViewGroup; import android.widget.ImageView; import com.facebook.rebound.SimpleSpringListener; @@ -30,9 +29,9 @@ public class ChatHead extends ImageView implements Sprin 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 ChatHeadContainer container; private State state; private T key; private float downX = -1; @@ -49,30 +48,39 @@ public class ChatHead extends ImageView implements Sprin private Spring yPositionSpring; private Bundle extras; private ImageView imageView; + private boolean isHero; public ChatHead(Context context) { super(context); - init(); + throw new IllegalArgumentException("This constructor cannot be used"); } public ChatHead(Context context, AttributeSet attrs) { super(context, attrs); - init(); + throw new IllegalArgumentException("This constructor cannot be used"); } public ChatHead(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(); + throw new IllegalArgumentException("This constructor cannot be used"); } - public ChatHead(ChatHeadContainer container, SpringSystem springsHolder, Context context, boolean isSticky) { + public ChatHead(ChatHeadManager manager, SpringSystem springsHolder, Context context, boolean isSticky) { super(context); - this.container = container; + 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; } @@ -94,12 +102,16 @@ public boolean isSticky() { } private void init() { - setLayoutParams(new ViewGroup.LayoutParams(container.getConfig().getHeadWidth(), container.getConfig().getHeadHeight())); xPositionListener = new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - setTranslationX((float) spring.getCurrentValue()); + manager.getChatHeadContainer().setViewX(ChatHead.this, (int)spring.getCurrentValue(), isHero); + } + + @Override + public void onSpringAtRest(Spring spring) { + super.onSpringAtRest(spring); } }; xPositionSpring = springSystem.createSpring(); @@ -110,7 +122,12 @@ public void onSpringUpdate(Spring spring) { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - setTranslationY((float) spring.getCurrentValue()); + manager.getChatHeadContainer().setViewY(ChatHead.this, (int)spring.getCurrentValue(), isHero); + } + + @Override + public void onSpringAtRest(Spring spring) { + super.onSpringAtRest(spring); } }; yPositionSpring = springSystem.createSpring(); @@ -136,7 +153,7 @@ public int getUnreadCount() { public void setUnreadCount(int unreadCount) { if (unreadCount != this.unreadCount) { - container.reloadDrawable(key); + manager.reloadDrawable(key); } this.unreadCount = unreadCount; } @@ -165,21 +182,21 @@ public void onSpringUpdate(Spring spring) { if (spring != activeHorizontalSpring && spring != activeVerticalSpring) return; int totalVelocity = (int) Math.hypot(activeHorizontalSpring.getVelocity(), activeVerticalSpring.getVelocity()); - if (container.getActiveArrangement() != null) - container.getActiveArrangement().onSpringUpdate(this, isDragging, container.getMaxWidth(), container.getMaxHeight(), spring, activeHorizontalSpring, activeVerticalSpring, totalVelocity); + if (manager.getActiveArrangement() != null) + manager.getActiveArrangement().onSpringUpdate(this, isDragging, manager.getMaxWidth(), manager.getMaxHeight(), spring, activeHorizontalSpring, activeVerticalSpring, totalVelocity); } } @Override public void onSpringAtRest(Spring spring) { - if (container.getListener() != null) - container.getListener().onChatHeadAnimateEnd(this); + if (manager.getListener() != null) + manager.getListener().onChatHeadAnimateEnd(this); } @Override public void onSpringActivate(Spring spring) { - if (container.getListener() != null) - container.getListener().onChatHeadAnimateStart(this); + if (manager.getListener() != null) + manager.getListener().onChatHeadAnimateStart(this); } @Override @@ -209,8 +226,8 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { float rawY = event.getRawY(); float offsetX = rawX - downX; float offsetY = rawY - downY; - boolean showCloseButton = container.getActiveArrangement().shouldShowCloseButton(this); - event.offsetLocation(getTranslationX(), getTranslationY()); + 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(); @@ -235,23 +252,23 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { if (Math.hypot(offsetX, offsetY) > touchSlop) { isDragging = true; if (showCloseButton) { - container.getCloseButton().appear(); + manager.getCloseButton().appear(); } } velocityTracker.addMovement(event); if (isDragging) { - container.getCloseButton().pointTo(rawX, rawY); - if (container.getActiveArrangement().canDrag(this)) { - double distanceCloseButtonFromHead = container.getDistanceCloseButtonFromHead(rawX, rawY); + 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 = container.getChatHeadCoordsForCloseButton(this); + int[] coords = manager.getChatHeadCoordsForCloseButton(this); activeHorizontalSpring.setEndValue(coords[0]); activeVerticalSpring.setEndValue(coords[1]); - container.getCloseButton().onCapture(); + manager.getCloseButton().onCapture(); } else { setState(ChatHead.State.FREE); @@ -259,7 +276,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { activeVerticalSpring.setSpringConfig(SpringConfigsHolder.DRAGGING); activeHorizontalSpring.setCurrentValue(downTranslationX + offsetX); activeVerticalSpring.setCurrentValue(downTranslationY + offsetY); - container.getCloseButton().onRelease(); + manager.getCloseButton().onRelease(); } velocityTracker.computeCurrentVelocity(1000); @@ -279,7 +296,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { velocityTracker.recycle(); velocityTracker = null; if(xPositionSpring!=null && yPositionSpring!=null) { - boolean touchUpHandled = container.getActiveArrangement().handleTouchUp(this, xVelocity, yVelocity, activeHorizontalSpring, activeVerticalSpring, wasDragging); + boolean touchUpHandled = manager.getActiveArrangement().handleTouchUp(this, xVelocity, yVelocity, activeHorizontalSpring, activeVerticalSpring, wasDragging); } } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadArrangement.java index 8257c1a..c7df573 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadArrangement.java @@ -1,6 +1,5 @@ package com.flipkart.chatheads.ui; -import android.graphics.Canvas; import android.os.Bundle; import android.view.MotionEvent; @@ -10,9 +9,9 @@ * Created by kirankumar on 13/02/15. */ public abstract class ChatHeadArrangement { - public abstract void setContainer(ChatHeadContainer container); + public abstract void setContainer(ChatHeadManager container); - public abstract void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, int maxHeight, boolean animated); + public abstract void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated); public abstract void onDeactivate(int maxWidth, int maxHeight); @@ -22,15 +21,13 @@ public boolean handleRawTouchEvent(MotionEvent event) { return false; } - public void onDraw(Canvas canvas) {}; - public abstract boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVelocity, Spring activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging); public abstract void onChatHeadAdded(ChatHead chatHead, boolean animated); public abstract void onChatHeadRemoved(ChatHead removed); - public abstract void onCapture(ChatHeadContainer container, ChatHead activeChatHead); + public abstract void onCapture(ChatHeadManager container, ChatHead activeChatHead); public abstract void selectChatHead(ChatHead chatHead); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java index a58481c..adf6e4e 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java @@ -3,9 +3,6 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; import android.widget.ImageView; import com.facebook.rebound.SimpleSpringListener; @@ -26,11 +23,13 @@ public class ChatHeadCloseButton extends ImageView { private Spring ySpring; private boolean disappeared; private CloseButtonListener listener; - private ChatHeadContainer chatHeadContainer; + private ChatHeadManager chatHeadManager; + private int centerX; + private int centerY; - public ChatHeadCloseButton(Context context, ChatHeadContainer container) { + public ChatHeadCloseButton(Context context, ChatHeadManager manager, int maxHeight, int maxWidth) { super(context); - init(container); + init(manager, maxHeight, maxWidth); } public void setListener(CloseButtonListener listener) { @@ -41,20 +40,20 @@ public boolean isDisappeared() { return disappeared; } - private void init(ChatHeadContainer container) { - this.chatHeadContainer = container; + private void init(final ChatHeadManager manager, int maxHeight, int maxWidth) { + this.chatHeadManager = manager; + setImageResource(R.drawable.dismiss_big); - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(chatHeadContainer.getConfig().getCloseButtonWidth(), chatHeadContainer.getConfig().getCloseButtonHeight()); - layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; - layoutParams.bottomMargin = chatHeadContainer.getConfig().getCloseButtonBottomMargin(); - setLayoutParams(layoutParams); SpringSystem springSystem = SpringSystem.create(); xSpring = springSystem.createSpring(); xSpring.addListener(new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - setTranslationX((float) spring.getCurrentValue()); + int x = centerX + (int) spring.getCurrentValue() - getMeasuredWidth()/2; + manager.getChatHeadContainer().setViewX(ChatHeadCloseButton.this, x, false); +// System.out.println("spring x = [" + x + "] center "+centerX); + } }); ySpring = springSystem.createSpring(); @@ -62,7 +61,9 @@ public void onSpringUpdate(Spring spring) { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - setTranslationY((float) spring.getCurrentValue()); + int y = centerY + (int) spring.getCurrentValue() - getMeasuredHeight()/2; + manager.getChatHeadContainer().setViewY(ChatHeadCloseButton.this, y, false); +// System.out.println("spring y = [" + y + "] center "+centerY); } }); scaleSpring = springSystem.createSpring(); @@ -77,7 +78,7 @@ public void onSpringUpdate(Spring spring) { } public void appear() { - if(isEnabled()) { + if (isEnabled()) { ySpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); xSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); scaleSpring.setEndValue(.8f); @@ -96,16 +97,16 @@ public void onRelease() { } public void disappear(boolean immediate, boolean animate) { - ySpring.setEndValue(mParentHeight - getTop()); + ySpring.setEndValue(mParentHeight); ySpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); xSpring.setEndValue(0); scaleSpring.setEndValue(0.1f); if (!animate) { - ySpring.setCurrentValue(mParentHeight-getTop(), true); + ySpring.setCurrentValue(mParentHeight, true); xSpring.setCurrentValue(0, true); } disappeared = true; - if(listener!=null) listener.onCloseButtonDisappear(); + if (listener != null) listener.onCloseButtonDisappear(); } @@ -117,18 +118,21 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { } public void onParentHeightRefreshed() { - mParentWidth = ((View) getParent()).getMeasuredWidth(); - mParentHeight = ((View) getParent()).getMeasuredHeight(); + mParentWidth = chatHeadManager.getMaxWidth(); + mParentHeight = chatHeadManager.getMaxHeight(); + this.centerX = (int) ((float)mParentWidth * 0.5f); + this.centerY = (int) ((float)mParentHeight * 0.9f); } public void pointTo(float x, float y) { - if(isEnabled()) { + if (isEnabled()) { double translationX = getTranslationFromSpring(x, PERC_PARENT_WIDTH, mParentWidth); double translationY = getTranslationFromSpring(y, PERC_PARENT_HEIGHT, mParentHeight); +// System.out.println("translationY = " + translationY); if (!disappeared) { xSpring.setEndValue(translationX); ySpring.setEndValue(translationY); - if(listener!=null) listener.onCloseButtonAppear(); + if (listener != null) listener.onCloseButtonAppear(); } } } @@ -152,6 +156,7 @@ public int getEndValueY() { public interface CloseButtonListener { void onCloseButtonAppear(); + void onCloseButtonDisappear(); } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java old mode 100755 new mode 100644 index c3930b1..3abdb88 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java @@ -1,710 +1,39 @@ package com.flipkart.chatheads.ui; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Canvas; -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.support.v4.app.FragmentTransaction; -import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewManager; import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.facebook.rebound.SpringConfigRegistry; -import com.facebook.rebound.SpringSystem; -import com.flipkart.chatheads.R; +import com.facebook.rebound.SimpleSpringListener; +import com.facebook.rebound.Spring; 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 ChatHeadContainer extends FrameLayout implements ChatHeadCloseButton.CloseButtonListener { +/** + * Created by kiran.kumar on 27/10/16. + */ +public interface ChatHeadContainer { + DisplayMetrics getDisplayMetrics(); - private static final int OVERLAY_TRANSITION_DURATION = 200; - private final Map, ChatHeadArrangement> arrangements = new HashMap<>(3); - private List> chatHeads; - private int maxWidth; - private int maxHeight; - private ChatHeadCloseButton closeButton; - private ChatHeadArrangement activeArrangement; - private ChatHeadViewAdapter viewAdapter; - private ChatHeadOverlayView overlayView; - private OnItemSelectedListener 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; + ViewGroup.LayoutParams createLayoutParams(int height, int width, int gravity, int bottomMargin); - public ChatHeadContainer(Context context) { - super(context); - init(context, new ChatHeadDefaultConfig(context)); - } + int getContainerHeight(); - public ChatHeadContainer(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, new ChatHeadDefaultConfig(context)); - } + int getContainerWidth(); - public ChatHeadContainer(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, new ChatHeadDefaultConfig(context)); - } + void setViewX(View view, int xPosition, boolean isHero); - public DisplayMetrics getDisplayMetrics() { - return displayMetrics; - } + void setViewY(View view, int yPosition, boolean isHero); - public ChatHeadListener getListener() { - return listener; - } + int getViewX(View view); - public void setListener(ChatHeadListener listener) { - this.listener = listener; - } + int getViewY(View view); - public List> getChatHeads() { - return chatHeads; - } + void setZOrder(View view, int zIndex); - public ChatHeadViewAdapter getViewAdapter() { - return viewAdapter; - } + void addView(View view, ViewGroup.LayoutParams layoutParams); - public void setViewAdapter(ChatHeadViewAdapter chatHeadViewAdapter) { - this.viewAdapter = chatHeadViewAdapter; - } - - public ChatHeadCloseButton getCloseButton() { - return closeButton; - } - - public int getMaxWidth() { - return maxWidth; - } - - public int getMaxHeight() { - return maxHeight; - } - - public Class getArrangementType() { - if (activeArrangement != null) { - return activeArrangement.getClass(); - } else if (requestedArrangement != null) { - return requestedArrangement.getArrangement(); - } - return null; - } - - public ChatHeadArrangement getActiveArrangement() { - if (activeArrangement != null) { - return activeArrangement; - } - return null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - return super.onTouchEvent(event); - } - - /** - * Selects the chat head. Very similar to performing touch up on it. - * - * @param chatHead - */ - public void selectChatHead(ChatHead chatHead) { - if (activeArrangement != null) - activeArrangement.selectChatHead(chatHead); - } - - public void selectChatHead(T key) { - ChatHead chatHead = findChatHeadByKey(key); - if (chatHead != null) { - selectChatHead(chatHead); - } - } - - /** - * Returns the fragment for the key if its already present. If createIfRequired is set to true, it will create and return it. - * - * @param key - * @param createIfRequired - * @return - */ - public Fragment getFragment(T key, boolean createIfRequired) { - return getFragment(findChatHeadByKey(key), createIfRequired); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - maxWidth = getMeasuredWidth(); - maxHeight = getMeasuredHeight(); - if (requestedArrangement != null) setArrangementImpl(requestedArrangement); - requestedArrangement = null; - - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (activeArrangement != null) { - activeArrangement.handleRawTouchEvent(ev); - } - return super.dispatchTouchEvent(ev); - } - - /** - * Adds and returns the created chat head - * - * @param isSticky If sticky is true, then this chat head will never be auto removed when size exceeds. - * Sticky chat heads can never be removed - * @return - */ - public ChatHead addChatHead(T key, boolean isSticky, boolean animated) { - ChatHead chatHead = findChatHeadByKey(key); - if (chatHead == null) { - chatHead = new ChatHead(this, springSystem, getContext(), isSticky); - chatHead.setKey(key); - chatHeads.add(chatHead); - addView(chatHead); - 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; - } - - public ChatHead findChatHeadByKey(T key) { - for (ChatHead chatHead : chatHeads) { - if (chatHead.getKey().equals(key)) - return chatHead; - } - - return null; - } - - public void reloadDrawable(T key) { - Drawable chatHeadDrawable = viewAdapter.getChatHeadDrawable(key); - if (chatHeadDrawable != null) { - findChatHeadByKey(key).setImageDrawable(viewAdapter.getChatHeadDrawable(key)); - } - } - - /** - * @param userTriggered if true this means that the chat head was removed by user action (drag to bottom) - */ - public void removeAllChatHeads(boolean userTriggered) { - for (Iterator> iterator = chatHeads.iterator(); iterator.hasNext(); ) { - ChatHead chatHead = iterator.next(); - iterator.remove(); - onChatHeadRemoved(chatHead, userTriggered); - } - } - - /** - * Removed the chat head and calls the onChatHeadRemoved listener - * - * @param key - * @param userTriggered if true this means that the chat head was removed by user action (drag to bottom) - * @return - */ - 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(); - removeView(chatHead); - if (activeArrangement != null) - activeArrangement.onChatHeadRemoved(chatHead); - if (listener != null) { - listener.onChatHeadRemoved(chatHead.getKey(), userTriggered); - } - } - } - - public ChatHeadOverlayView getOverlayView() { - return overlayView; - } - - private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getMetrics(metrics); - this.displayMetrics = metrics; - setConfig(chatHeadDefaultConfig); - chatHeads = new ArrayList<>(5); - LayoutInflater.from(context).inflate(R.layout.arrow_layout, this, true); - UpArrowLayout arrowLayout = (UpArrowLayout) findViewById(R.id.arrow_layout); - arrowLayout.setVisibility(View.GONE); - springSystem = SpringSystem.create(); - closeButton = new ChatHeadCloseButton(getContext(), this); - closeButton.setListener(this); - addView(closeButton); - closeButtonShadow = new ImageView(getContext()); - LayoutParams shadowLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - shadowLayoutParams.gravity = Gravity.BOTTOM; - closeButtonShadow.setLayoutParams(shadowLayoutParams); - closeButtonShadow.setImageResource(R.drawable.dismiss_shadow); - closeButtonShadow.setVisibility(View.GONE); - addView(closeButtonShadow); - arrangements.put(MinimizedArrangement.class, new MinimizedArrangement(this)); - arrangements.put(MaximizedArrangement.class, new MaximizedArrangement(this)); - arrangements.put(CircularArrangement.class, new CircularArrangement(this)); - setupOverlay(context); - 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); - addView(overlayView, 0); - } - - 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 - closeButton.getMeasuredWidth() / 2; - double yDiff = touchY - top - closeButton.getTranslationY() - closeButton.getMeasuredHeight() / 2; - double distance = Math.hypot(xDiff, yDiff); - return distance; - } - } - - void captureChatHeads(ChatHead causingChatHead) { - activeArrangement.onCapture(this, causingChatHead); - } - - - public ChatHeadArrangement getArrangement(Class arrangementType) { - return arrangements.get(arrangementType); - } - - public void setArrangement(final Class arrangement, Bundle extras) { - setArrangement(arrangement, extras, true); - } - - public void setArrangement(final Class arrangement, Bundle extras, boolean animated) { - this.requestedArrangement = new ArrangementChangeRequest(arrangement, extras, animated); - requestLayout(); - } - - /** - * Should only be called after onMeasure - * - * @param requestedArrangement - */ - private void setArrangementImpl(ArrangementChangeRequest requestedArrangement) { - ChatHeadArrangement chatHeadArrangement = arrangements.get(requestedArrangement.getArrangement()); - ChatHeadArrangement oldArrangement = null; - ChatHeadArrangement newArrangement = chatHeadArrangement; - Bundle extras = requestedArrangement.getExtras(); - if (extras == null) extras = new Bundle(); - - if (activeArrangement != null && chatHeadArrangement != activeArrangement) { - extras.putAll(activeArrangement.getRetainBundle()); - activeArrangement.onDeactivate(maxWidth, maxHeight); - oldArrangement = activeArrangement; - } - activeArrangement = chatHeadArrangement; - activeArrangementBundle = extras; - chatHeadArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangement.isAnimated()); - if (listener != null) listener.onChatHeadArrangementChanged(oldArrangement, newArrangement); - } - - 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; - } - } - - 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; - } - } - - 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; - } - - public void setOnItemSelectedListener(ChatHeadContainer.OnItemSelectedListener onItemSelectedListener) { - this.itemSelectedListener = onItemSelectedListener; - } - - public boolean onItemSelected(ChatHead chatHead) { - return itemSelectedListener != null && itemSelectedListener.onChatHeadSelected(chatHead.getKey(), chatHead); - } - - public void onItemRollOver(ChatHead chatHead) { - if (itemSelectedListener != null) - itemSelectedListener.onChatHeadRollOver(chatHead.getKey(), chatHead); - } - - public void onItemRollOut(ChatHead chatHead) { - if (itemSelectedListener != null) - itemSelectedListener.onChatHeadRollOut(chatHead.getKey(), chatHead); - } - - public void bringToFront(ChatHead chatHead) { - if (activeArrangement != null) { - activeArrangement.bringToFront(chatHead); - } - } - - @Override - public void onCloseButtonAppear() { - closeButtonShadow.setVisibility(View.VISIBLE); - } - - @Override - public void onCloseButtonDisappear() { - closeButtonShadow.setVisibility(View.GONE); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - activeArrangement.onDraw(canvas); - } - - public void reloadFragment(T key) { - removeFragment(findChatHeadByKey(key)); - if (activeArrangement != null) { - activeArrangement.onReloadFragment(findChatHeadByKey(key)); - } - } - - public SpringSystem getSpringSystem() { - return springSystem; - } - - Fragment addFragment(ChatHead activeChatHead, ViewGroup parent) { - try { - FragmentManager manager = getFragmentManager(); - FragmentTransaction transaction = manager.beginTransaction(); - - Fragment fragment = getFragmentManager().findFragmentByTag(activeChatHead.getKey().toString()); - - if (fragment == null) { - fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); - transaction.add(parent.getId(), fragment, activeChatHead.getKey().toString()); - } else { - if (fragment.isDetached()) { - transaction.attach(fragment); - } - } - if (fragment != currentFragment && currentFragment != null) { - transaction.detach(currentFragment); - } - currentFragment = fragment; - transaction.commitAllowingStateLoss(); - manager.executePendingTransactions(); - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } - return null; - } - - Fragment removeFragment(ChatHead chatHead) { - try { - FragmentManager manager = getFragmentManager(); - FragmentTransaction transaction = manager.beginTransaction(); - Fragment fragment = getFragment(chatHead, false); - if (fragment == null) { - //we dont have it in our cache. So we create it and add it - } else { - //we have added it already sometime earlier. So re-attach it. - transaction.remove(fragment); - - } - if (fragment == currentFragment) { - currentFragment = null; - } - transaction.commitAllowingStateLoss(); - manager.executePendingTransactions(); - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } - return null; - } - - Fragment detachFragment(ChatHead chatHead) { - try { - FragmentManager fragmentManager = getFragmentManager(); - Fragment fragment = getFragment(chatHead, false); - if (fragment != null) { - - FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - if (!fragment.isDetached()) { - fragmentTransaction.detach(fragment); - } - fragmentTransaction.commitAllowingStateLoss(); - } - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } - return null; - } - - - Fragment getFragment(ChatHead activeChatHead, boolean createIfRequired) { - String tag = ""; - if (activeChatHead != null) { - tag = activeChatHead.getKey().toString(); - } - Fragment fragment = getFragmentManager().findFragmentByTag(tag); - if (fragment == null && createIfRequired) { - fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); - } - return fragment; - } - - - public FragmentManager getFragmentManager() { - if (fragmentManager == null) { - if (getViewAdapter() == null) - throw new IllegalStateException(ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); - fragmentManager = getViewAdapter().getFragmentManager(); - if (fragmentManager == null) - throw new IllegalStateException(FragmentManager.class.getSimpleName() + " returned from " + ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); - } - return fragmentManager; - } - - public ChatHeadConfig getConfig() { - return config; - } - - public void setConfig(ChatHeadConfig config) { - this.config = config; - if (closeButton != null) { - FrameLayout.LayoutParams params = (LayoutParams) closeButton.getLayoutParams(); - params.width = config.getCloseButtonWidth(); - params.height = config.getCloseButtonHeight(); - params.bottomMargin = config.getCloseButtonBottomMargin(); - closeButton.setLayoutParams(params); - } - for (Map.Entry, ChatHeadArrangement> arrangementEntry : arrangements.entrySet()) { - arrangementEntry.getValue().onConfigChanged(config); - } - - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - if (activeArrangement != null) { - savedState.setActiveArrangement(activeArrangement.getClass()); - savedState.setActiveArrangementBundle(activeArrangement.getRetainBundle()); - } - LinkedHashMap chatHeadState = new LinkedHashMap<>(); - for (ChatHead chatHead : chatHeads) { - T key = chatHead.getKey(); - boolean sticky = chatHead.isSticky(); - chatHeadState.put(key, sticky); - } - savedState.setChatHeads(chatHeadState); - return savedState; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - SavedState savedState = (SavedState) state; - final Class activeArrangementClass = savedState.getActiveArrangement(); - final Bundle activeArrangementBundle = savedState.getActiveArrangementBundle(); - final Map chatHeads = savedState.getChatHeads(); - for (Map.Entry entry : chatHeads.entrySet()) { - T key = (T) entry.getKey(); - Boolean sticky = entry.getValue(); - addChatHead(key, sticky, false); - } - if (activeArrangementClass != null) { - setArrangement(activeArrangementClass, activeArrangementBundle, false); - } - super.onRestoreInstanceState(savedState.getSuperState()); - } else { - super.onRestoreInstanceState(state); - } - - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (closeButton != null) { - closeButton.onParentHeightRefreshed(); - } - } - - public interface OnItemSelectedListener { - /** - * Will be called whenever a chat head is clicked. - * If you return false from here, the arrangement will continue whatever its supposed to do. - * If you return true from here, the arrangement will stop the action it normally does after click. - * - * @param key - * @param chatHead - * @return true if you want to take control. false if you dont care. - */ - boolean onChatHeadSelected(T key, ChatHead chatHead); - - void onChatHeadRollOver(T key, ChatHead chatHead); - - void onChatHeadRollOut(T key, ChatHead chatHead); - } - - static class SavedState extends BaseSavedState { - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - - @Override - public SavedState createFromParcel(Parcel source) { - return new SavedState(source); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - private Class activeArrangement; - private Bundle activeArrangementBundle; - private LinkedHashMap chatHeads; - - public SavedState(Parcel source) { - super(source); - activeArrangement = (Class) source.readSerializable(); - activeArrangementBundle = source.readBundle(); - chatHeads = (LinkedHashMap) source.readSerializable(); - } - - public SavedState(Parcelable superState) { - super(superState); - } - - public Class getActiveArrangement() { - return activeArrangement; - } - - public void setActiveArrangement(Class 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 getChatHeads() { - return chatHeads; - } - - public void setChatHeads(LinkedHashMap chatHeads) { - this.chatHeads = chatHeads; - } - } - - private class ArrangementChangeRequest { - private final Bundle extras; - private final Class arrangement; - private final boolean animated; - - public ArrangementChangeRequest(Class arrangement, Bundle extras, boolean animated) { - this.arrangement = arrangement; - this.extras = extras; - this.animated = animated; - } - - public Bundle getExtras() { - return extras; - } - - public Class getArrangement() { - return arrangement; - } - - public boolean isAnimated() { - return animated; - } - } -} + void removeView(View view); +} \ No newline at end of file diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java new file mode 100644 index 0000000..162b725 --- /dev/null +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java @@ -0,0 +1,172 @@ +package com.flipkart.chatheads.ui; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.util.DisplayMetrics; +import android.view.ViewGroup; + +import com.facebook.rebound.SpringSystem; + +import java.io.Serializable; +import java.util.List; + +/** + * Created by kiran.kumar on 27/10/16. + */ + +public interface ChatHeadManager { + ChatHeadListener getListener(); + + void setListener(ChatHeadListener listener); + + List> getChatHeads(); + + ChatHeadViewAdapter getViewAdapter(); + + void setViewAdapter(ChatHeadViewAdapter chatHeadViewAdapter); + + ChatHeadCloseButton getCloseButton(); + + Class getArrangementType(); + + ChatHeadArrangement getActiveArrangement(); + + /** + * Selects the chat head. Very similar to performing touch up on it. + * + * @param chatHead + */ + void selectChatHead(ChatHead chatHead); + + void selectChatHead(T key); + + /** + * Returns the fragment for the key if its already present. If createIfRequired is set to true, it will create and return it. + * + * @param key + * @param createIfRequired + * @return + */ + Fragment getFragment(T key, boolean createIfRequired); + + /** + * Should be called when measuring of the container is done. + * Typically called from onMeasure or onLayout + * Only when {@link ChatHeadContainer#getContainerHeight()} && {@link ChatHeadContainer#getContainerWidth()} returns a positive value will arrangements start working + */ + void onMeasure(); + + /** + * Adds and returns the created chat head + * + * @param isSticky If sticky is true, then this chat head will never be auto removed when size exceeds. + * Sticky chat heads can never be removed + * @return + */ + ChatHead addChatHead(T key, boolean isSticky, boolean animated); + + ChatHead findChatHeadByKey(T key); + + void reloadDrawable(T key); + + /** + * @param userTriggered if true this means that the chat head was removed by user action (drag to bottom) + */ + void removeAllChatHeads(boolean userTriggered); + + /** + * Removed the chat head and calls the onChatHeadRemoved listener + * + * @param key + * @param userTriggered if true this means that the chat head was removed by user action (drag to bottom) + * @return + */ + boolean removeChatHead(T key, boolean userTriggered); + + ChatHeadOverlayView getOverlayView(); + + void captureChatHeads(ChatHead causingChatHead); + + ChatHeadArrangement getArrangement(Class arrangementType); + + void setArrangement(Class arrangement, Bundle extras); + + void setArrangement(Class arrangement, Bundle extras, boolean animated); + + void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener); + + boolean onItemSelected(ChatHead chatHead); + + void onItemRollOver(ChatHead chatHead); + + void onItemRollOut(ChatHead chatHead); + + void onCloseButtonAppear(); + + void onCloseButtonDisappear(); + + void reloadFragment(T key); + + SpringSystem getSpringSystem(); + + Fragment addFragment(ChatHead activeChatHead, ViewGroup parent); + + Fragment removeFragment(ChatHead chatHead); + + Fragment detachFragment(ChatHead chatHead); + + FragmentManager getFragmentManager(); + + ChatHeadConfig getConfig(); + + void setConfig(ChatHeadConfig config); + + double getDistanceCloseButtonFromHead(float rawX, float rawY); + + void hideOverlayView(boolean animated); + + void showOverlayView(boolean animated); + + int[] getChatHeadCoordsForCloseButton(ChatHead chatHead); + + void bringToFront(ChatHead chatHead); + + UpArrowLayout getArrowLayout(); + + ChatHeadContainer getChatHeadContainer(); + + DisplayMetrics getDisplayMetrics(); + + int getMaxWidth(); + + int getMaxHeight(); + + Context getContext(); + + Parcelable onSaveInstanceState(Parcelable superState); + + void onRestoreInstanceState(Parcelable state); + + void onSizeChanged(int w, int h, int oldw, int oldh); + + + interface OnItemSelectedListener { + /** + * Will be called whenever a chat head is clicked. + * If you return false from here, the arrangement will continue whatever its supposed to do. + * If you return true from here, the arrangement will stop the action it normally does after click. + * + * @param key + * @param chatHead + * @return true if you want to take control. false if you dont care. + */ + boolean onChatHeadSelected(T key, ChatHead chatHead); + + void onChatHeadRollOver(T key, ChatHead chatHead); + + void onChatHeadRollOut(T key, ChatHead chatHead); + } +} diff --git a/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java index 1b4a0e4..1753fcd 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java @@ -33,7 +33,7 @@ public class CircularArrangement extends ChatHeadArrange private final ImageView pointerViewStatic; private int RADIUS; private boolean isActive = false; - private ChatHeadContainer container; + private ChatHeadManager manager; private ChatHead currentChatHead; private int maxWidth; private int maxHeight; @@ -42,16 +42,16 @@ public class CircularArrangement extends ChatHeadArrange private Bundle retainBundle; - public CircularArrangement(ChatHeadContainer container) { - this.container = container; - this.pointerViewMovable = new ImageView(container.getContext()); - this.pointerViewStatic = new ImageView(container.getContext()); + public CircularArrangement(ChatHeadManager manager) { + this.manager = manager; + this.pointerViewMovable = new ImageView(manager.getContext()); + this.pointerViewStatic = new ImageView(manager.getContext()); - container.addView(pointerViewMovable); - container.addView(pointerViewStatic); - this.pointerXSpring = container.getSpringSystem().createSpring(); - this.pointerYSpring = container.getSpringSystem().createSpring(); - this.pointerScaleSpring = container.getSpringSystem().createSpring(); + //manager.getViewManager().addView(pointerViewMovable,pointerViewMovable.getLayoutParams()); + //manager.getViewManager().addView(pointerViewStatic,pointerViewStatic.getLayoutParams()); + this.pointerXSpring = manager.getSpringSystem().createSpring(); + this.pointerYSpring = manager.getSpringSystem().createSpring(); + this.pointerScaleSpring = manager.getSpringSystem().createSpring(); this.pointerXSpring.addListener(new SimpleSpringListener() { @Override public void onSpringUpdate(Spring spring) { @@ -75,7 +75,7 @@ public void onSpringUpdate(Spring spring) { } }); - onConfigChanged(container.getConfig()); + onConfigChanged(manager.getConfig()); } @@ -98,21 +98,21 @@ public boolean canDrag(ChatHead chatHead) { @Override public void removeOldestChatHead() { - for (ChatHead chatHead : container.getChatHeads()) { + for (ChatHead chatHead : manager.getChatHeads()) { if (!chatHead.isSticky()) { - container.removeChatHead(chatHead.getKey(), false); + manager.removeChatHead(chatHead.getKey(), false); break; } } } @Override - public void setContainer(ChatHeadContainer container) { - this.container = container; + public void setContainer(ChatHeadManager container) { + this.manager = container; } @Override - public void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { + public void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { List chatHeads = container.getChatHeads(); RADIUS = container.getConfig().getCircularFanOutRadius(maxWidth, maxHeight); int headHeight = container.getConfig().getHeadHeight(); @@ -158,7 +158,7 @@ public void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, } isActive = true; - this.container = container; + this.manager = container; container.showOverlayView(true); container.getOverlayView().setOnClickListener(new View.OnClickListener() { @Override @@ -193,7 +193,7 @@ public boolean handleRawTouchEvent(MotionEvent event) { super.handleRawTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_MOVE) { boolean foundSpring = false; - List> chatHeads = container.getChatHeads(); + List> chatHeads = manager.getChatHeads(); for (ChatHead chatHead : chatHeads) { double distance = Math.hypot(event.getX() - pointerViewMovable.getMeasuredWidth() / 2 - chatHead.getTranslationX(), event.getY() - pointerViewMovable.getMeasuredHeight() / 2 - chatHead.getTranslationY()); if (distance < CLOSE_ATTRACTION_THRESHOLD) { @@ -204,11 +204,11 @@ public boolean handleRawTouchEvent(MotionEvent event) { pointerYSpring.setEndValue(chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2); pointerScaleSpring.setEndValue(1f); if (currentChatHead != chatHead) { - container.getOverlayView().drawPath(pointerViewStatic.getTranslationX() + pointerViewStatic.getMeasuredWidth() / 2, pointerViewStatic.getTranslationY() + pointerViewStatic.getMeasuredHeight() / 2, chatHead.getTranslationX() + chatHead.getMeasuredHeight() / 2, chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2); + manager.getOverlayView().drawPath(pointerViewStatic.getTranslationX() + pointerViewStatic.getMeasuredWidth() / 2, pointerViewStatic.getTranslationY() + pointerViewStatic.getMeasuredHeight() / 2, chatHead.getTranslationX() + chatHead.getMeasuredHeight() / 2, chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2); } currentChatHead = chatHead; if (rollOverChatHead != chatHead && isActive) { - container.onItemRollOver(chatHead); + manager.onItemRollOver(chatHead); rollOverChatHead = chatHead; } rollOverState = RollState.OVER; @@ -222,15 +222,15 @@ public boolean handleRawTouchEvent(MotionEvent event) { pointerXSpring.setEndValue(event.getX()); pointerYSpring.setEndValue(event.getY()); pointerScaleSpring.setEndValue(0.5f); - container.getOverlayView().clearPath(); + manager.getOverlayView().clearPath(); onRollOut(); currentChatHead = null; } } else if (event.getAction() == MotionEvent.ACTION_UP) { - container.getOverlayView().clearPath(); + manager.getOverlayView().clearPath(); onRollOut(); if (currentChatHead != null) { - boolean handled = container.onItemSelected(currentChatHead); + boolean handled = manager.onItemSelected(currentChatHead); if (!handled) { deactivate(); } @@ -246,7 +246,7 @@ public boolean handleRawTouchEvent(MotionEvent event) { private void onRollOut() { if (rollOverState != RollState.OUT && rollOverChatHead != null) { - container.onItemRollOut(rollOverChatHead); + manager.onItemRollOut(rollOverChatHead); rollOverChatHead = null; } rollOverState = RollState.OUT; @@ -312,13 +312,13 @@ private List findContainingQuadrants(Rect firstRect, Rect secondRect) } private void deactivate() { - container.setArrangement(MinimizedArrangement.class, null); + manager.setArrangement(MinimizedArrangement.class, null); } @Override public Integer getHeroIndex() { int heroIndex = 0; - List> chatHeads = container.getChatHeads(); + List> chatHeads = manager.getChatHeads(); int i = 0; for (ChatHead chatHead : chatHeads) { if (currentChatHead == chatHead) { @@ -336,7 +336,7 @@ public void onDeactivate(int maxWidth, int maxHeight) { isActive = false; pointerViewMovable.setVisibility(View.GONE); pointerViewStatic.setVisibility(View.GONE); - this.container.hideOverlayView(true); + this.manager.hideOverlayView(true); currentChatHead = null; } @@ -348,7 +348,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW @Override public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVelocity, Spring activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging) { if (isActive) { - boolean handled = container.onItemSelected(activeChatHead); + boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { deactivate(); } @@ -361,16 +361,16 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci @Override public void onChatHeadAdded(ChatHead chatHead, boolean animated) { - onActivate(container, retainBundle, maxWidth, maxHeight, true); + onActivate(manager, retainBundle, maxWidth, maxHeight, true); } @Override public void onChatHeadRemoved(ChatHead removed) { - onActivate(container, retainBundle, maxWidth, maxHeight, true); + onActivate(manager, retainBundle, maxWidth, maxHeight, true); } @Override - public void onCapture(ChatHeadContainer container, ChatHead activeChatHead) { + public void onCapture(ChatHeadManager container, ChatHead activeChatHead) { } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java new file mode 100644 index 0000000..c025905 --- /dev/null +++ b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java @@ -0,0 +1,91 @@ +package com.flipkart.chatheads.ui; + +import android.content.Context; +import android.graphics.Color; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.facebook.rebound.Spring; + +/** + * Created by kiran.kumar on 02/11/16. + */ + +public abstract class FrameChatHeadContainer implements ChatHeadContainer { + + private final FrameLayout frameLayout; + private final Context context; + DisplayMetrics displayMetrics = new DisplayMetrics(); + + public FrameChatHeadContainer(Context context) { + this.context = context; + FrameLayout frameLayout = new FrameLayout(context); + this.frameLayout = frameLayout; + addContainer(frameLayout, false); + } + + public Context getContext() { + return context; + } + + public FrameLayout getFrameLayout() { + return frameLayout; + } + + @Override + public void addView(View view, ViewGroup.LayoutParams layoutParams) { + frameLayout.addView(view, layoutParams); + } + + @Override + public void removeView(View view) { + frameLayout.removeView(view); + } + + @Override + public ViewGroup.LayoutParams createLayoutParams(int height, int width, int gravity, int bottomMargin) { + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height); + layoutParams.gravity = gravity; + layoutParams.bottomMargin = bottomMargin; + return layoutParams; + } + + @Override + public void setViewX(View view, int xPosition, boolean isHero) { + view.setTranslationX(xPosition); + } + + @Override + public void setViewY(View view, int yPosition, boolean isHero) { + view.setTranslationY(yPosition); + } + + @Override + public DisplayMetrics getDisplayMetrics() { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics; + } + + @Override + public int getViewX(View view) { + return (int) view.getTranslationX(); + } + + @Override + public int getViewY(View view) { + return (int) view.getTranslationY(); + } + + @Override + public void setZOrder(View view, int zIndex) { + view.bringToFront(); + } + + public abstract void addContainer(View container, boolean focusable); + +} diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java index 4b390b9..78ddf6f 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java @@ -8,7 +8,6 @@ import android.view.View; import com.facebook.rebound.Spring; -import com.flipkart.chatheads.R; import com.flipkart.chatheads.ChatHeadUtils; import java.io.Serializable; @@ -21,7 +20,7 @@ public class MaximizedArrangement extends ChatHeadArrang private static double MAX_DISTANCE_FROM_ORIGINAL; private static int MIN_VELOCITY_TO_POSITION_BACK; private final Map positions = new ArrayMap<>(); - private ChatHeadContainer container; + private ChatHeadManager manager; private int maxWidth; private int maxHeight; private ChatHead currentChatHead = null; @@ -31,19 +30,19 @@ public class MaximizedArrangement extends ChatHeadArrang private boolean isActive = false; - public MaximizedArrangement(ChatHeadContainer container) { - this.container = container; + public MaximizedArrangement(ChatHeadManager manager) { + this.manager = manager; } @Override - public void setContainer(ChatHeadContainer container) { - this.container = container; + public void setContainer(ChatHeadManager container) { + this.manager = container; } @Override - public void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { - this.container = container; + public void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { + this.manager = container; this.maxWidth = maxWidth; this.maxHeight = maxHeight; MIN_VELOCITY_TO_POSITION_BACK = ChatHeadUtils.dpToPx(container.getDisplayMetrics(), 50); @@ -103,10 +102,10 @@ public void onClick(View v) { @Override public void onDeactivate(int maxWidth, int maxHeight) { if (currentChatHead != null) { - container.detachFragment(currentChatHead); + manager.detachFragment(currentChatHead); } hideView(); - container.hideOverlayView(true); + manager.hideOverlayView(true); positions.clear(); isActive = false; } @@ -131,13 +130,13 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci return true; } else { if (activeChatHead != currentChatHead) { - boolean handled = container.onItemSelected(activeChatHead); + boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { selectTab(activeChatHead); return true; } } - boolean handled = container.onItemSelected(activeChatHead); + boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { deactivate(); } @@ -183,7 +182,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW /** Bounds Check **/ if (spring == activeHorizontalSpring && !isDragging) { double xPosition = activeHorizontalSpring.getCurrentValue(); - if (xPosition + container.getConfig().getHeadWidth() > maxWidth && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { + 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()) { @@ -192,7 +191,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW } else if (spring == activeVerticalSpring && !isDragging) { double yPosition = activeVerticalSpring.getCurrentValue(); - if (yPosition + container.getConfig().getHeadHeight() > maxHeight && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.NOT_DRAGGING && !activeHorizontalSpring.isOvershooting()) { + 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()) { @@ -213,8 +212,8 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW if (!isDragging) { /** Capturing check **/ - int[] coords = container.getChatHeadCoordsForCloseButton(activeChatHead); - double distanceCloseButtonFromHead = container.getDistanceCloseButtonFromHead((float) activeHorizontalSpring.getCurrentValue() + container.getConfig().getHeadWidth() / 2, (float) activeVerticalSpring.getCurrentValue() + container.getConfig().getHeadHeight() / 2); + 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()) { @@ -232,13 +231,13 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW } if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeVerticalSpring.isAtRest()) { - container.getCloseButton().disappear(false, true); - container.captureChatHeads(activeChatHead); + manager.getCloseButton().disappear(false, true); + manager.captureChatHeads(activeChatHead); } if (!activeVerticalSpring.isAtRest()) { - container.getCloseButton().appear(); + manager.getCloseButton().appear(); } else { - container.getCloseButton().disappear(true, true); + manager.getCloseButton().disappear(true, true); } } } @@ -260,8 +259,7 @@ private void showOrHideView(ChatHead activeChatHead) { private UpArrowLayout getArrowLayout() { if (arrowLayout == null) { - arrowLayout = (UpArrowLayout) container.findViewById(R.id.arrow_layout); - + arrowLayout = manager.getArrowLayout(); } return arrowLayout; } @@ -290,11 +288,11 @@ private void showView(ChatHead activeChatHead, double dx, double dy, double dist private void pointTo(ChatHead activeChatHead) { UpArrowLayout arrowLayout = getArrowLayout(); - container.addFragment(activeChatHead, arrowLayout); + manager.addFragment(activeChatHead, arrowLayout); Point point = positions.get(activeChatHead); if (point != null) { - int padding = container.getConfig().getHeadVerticalSpacing(maxWidth, maxHeight); - arrowLayout.pointTo(point.x + container.getConfig().getHeadWidth() / 2, point.y + container.getConfig().getHeadHeight() + padding); + int padding = manager.getConfig().getHeadVerticalSpacing(maxWidth, maxHeight); + arrowLayout.pointTo(point.x + manager.getConfig().getHeadWidth() / 2, point.y + manager.getConfig().getHeadHeight() + padding); } } @@ -306,13 +304,13 @@ public void onChatHeadAdded(final ChatHead chatHead, final boolean animated) { spring.setCurrentValue(maxWidth).setAtRest(); spring = chatHead.getVerticalSpring(); spring.setCurrentValue(topPadding).setAtRest(); - onActivate(container, getBundleWithHero(), maxWidth, maxHeight, animated); + onActivate(manager, getBundleWithHero(), maxWidth, maxHeight, animated); } @Override public void onChatHeadRemoved(ChatHead removed) { - container.removeFragment(removed); + manager.removeFragment(removed); positions.remove(removed); boolean isEmpty = false; if (currentChatHead == removed) { @@ -325,7 +323,7 @@ public void onChatHeadRemoved(ChatHead removed) { } } if (!isEmpty) { - onActivate(container, getBundleWithHero(), maxWidth, maxHeight, true); + onActivate(manager, getBundleWithHero(), maxWidth, maxHeight, true); } else { deactivate(); } @@ -334,7 +332,7 @@ public void onChatHeadRemoved(ChatHead removed) { @Override - public void onCapture(ChatHeadContainer container, ChatHead activeChatHead) { + public void onCapture(ChatHeadManager container, ChatHead activeChatHead) { if (!activeChatHead.isSticky()) { container.removeChatHead(activeChatHead.getKey(), true); } @@ -348,7 +346,7 @@ public void selectChatHead(final ChatHead chatHead) { private ChatHead getNextBestChatHead() { ChatHead nextBestChatHead = null; - for (ChatHead head : container.getChatHeads()) { + for (ChatHead head : manager.getChatHeads()) { if (nextBestChatHead == null) { nextBestChatHead = head; } else if (head.getUnreadCount() >= nextBestChatHead.getUnreadCount()) { @@ -365,7 +363,7 @@ private Bundle getBundleWithHero() { } private void deactivate() { - container.setArrangement(MinimizedArrangement.class, getBundleWithHero()); + manager.setArrangement(MinimizedArrangement.class, getBundleWithHero()); hideView(); } @@ -375,7 +373,7 @@ private void deactivate() { @Override public Integer getHeroIndex() { int heroIndex = 0; - List> chatHeads = container.getChatHeads(); + List> chatHeads = manager.getChatHeads(); int i = 0; for (ChatHead chatHead : chatHeads) { if (currentChatHead == chatHead) { @@ -404,10 +402,10 @@ public boolean canDrag(ChatHead chatHead) { @Override public void removeOldestChatHead() { - for (ChatHead chatHead : container.getChatHeads()) { + for (ChatHead chatHead : manager.getChatHeads()) { //we dont remove sticky chat heads as well as the currently selected chat head if (!chatHead.isSticky() && chatHead != currentChatHead) { - container.removeChatHead(chatHead.getKey(), false); + manager.removeChatHead(chatHead.getKey(), false); break; } } @@ -417,18 +415,13 @@ public void removeOldestChatHead() { @Override public void bringToFront(final ChatHead chatHead) { //nothing to do, everything is in front. - container.post(new Runnable() { - @Override - public void run() { - selectChatHead(chatHead); - } - }); + selectChatHead(chatHead); } @Override public void onReloadFragment(ChatHead chatHead) { if (currentChatHead != null && chatHead == currentChatHead) { - container.addFragment(chatHead, getArrowLayout()); + manager.addFragment(chatHead, getArrowLayout()); } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java index cf45951..6846017 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java @@ -25,7 +25,7 @@ public class MinimizedArrangement extends ChatHeadArrang private int maxWidth; private int maxHeight; private boolean hasActivated = false; - private ChatHeadContainer container; + private ChatHeadManager manager; private SpringChain horizontalSpringChain; private SpringChain verticalSpringChain; private ChatHead hero; @@ -45,9 +45,9 @@ public void onSpringUpdate(Spring spring) { } }; - public MinimizedArrangement(ChatHeadContainer container) { - DELTA = ChatHeadUtils.dpToPx(container.getContext(), 5); - this.container = container; + public MinimizedArrangement(ChatHeadManager manager) { + this.manager = manager; + DELTA = ChatHeadUtils.dpToPx(this.manager.getContext(), 5); } public void setIdleStateX(int idleStateX) { @@ -63,12 +63,12 @@ public Point getIdleStatePosition() { } @Override - public void setContainer(ChatHeadContainer container) { - this.container = container; + public void setContainer(ChatHeadManager container) { + this.manager = container; } @Override - public void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { + public void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { if (horizontalSpringChain != null || verticalSpringChain != null) { onDeactivate(maxWidth, maxHeight); } @@ -82,13 +82,16 @@ public void onActivate(ChatHeadContainer container, Bundle extras, int maxWidth, 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) { @@ -107,7 +110,8 @@ public void onSpringUpdate(Spring spring) { }); currentSpring = verticalSpringChain.getAllSprings().get(verticalSpringChain.getAllSprings().size() - 1); currentSpring.setCurrentValue(chatHead.getVerticalSpring().getCurrentValue()); - chatHead.bringToFront(); + manager.getChatHeadContainer().setZOrder(chatHead,zIndex); + zIndex++; } } if (idleStateY == Integer.MIN_VALUE) { @@ -117,7 +121,7 @@ public void onSpringUpdate(Spring spring) { idleStateX = container.getConfig().getInitialPosition().x; } if (hero != null && hero.getHorizontalSpring()!=null && hero.getVerticalSpring()!=null ) { - hero.bringToFront(); + manager.getChatHeadContainer().setZOrder(hero,zIndex); horizontalSpringChain.addSpring(new SimpleSpringListener() { }); verticalSpringChain.addSpring(new SimpleSpringListener() { @@ -162,28 +166,28 @@ public void onChatHeadAdded(ChatHead chatHead, boolean animated) { chatHead.getVerticalSpring().setCurrentValue(hero.getVerticalSpring().getCurrentValue()); } - onActivate(container, null, maxWidth, maxHeight, animated); + onActivate(manager, null, maxWidth, maxHeight, animated); } @Override public void onChatHeadRemoved(ChatHead removed) { - container.removeFragment(removed); + manager.removeFragment(removed); if (removed == hero) { hero = null; } - onActivate(container, null, maxWidth, maxHeight, true); + onActivate(manager, null, maxWidth, maxHeight, true); } @Override - public void onCapture(ChatHeadContainer container, ChatHead activeChatHead) { + public void onCapture(ChatHeadManager container, ChatHead activeChatHead) { // we dont care about the active ones container.removeAllChatHeads(true); } @Override public void selectChatHead(ChatHead chatHead) { - //container.toggleArrangement(); + //manager.toggleArrangement(); } @Override @@ -215,7 +219,7 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging) { if (activeChatHead.getState() == ChatHead.State.FREE) { - if (Math.abs(xVelocity) < ChatHeadUtils.dpToPx(container.getDisplayMetrics(), 50)) { + if (Math.abs(xVelocity) < ChatHeadUtils.dpToPx(manager.getDisplayMetrics(), 50)) { if (activeHorizontalSpring.getCurrentValue() < (maxWidth - activeHorizontalSpring.getCurrentValue())) { xVelocity = -1; } else { @@ -228,7 +232,7 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci xVelocity = (newVelocity); } else if (xVelocity > 0) { - int newVelocity = (int) ((maxWidth - activeHorizontalSpring.getCurrentValue() - container.getConfig().getHeadWidth()) * SpringConfigsHolder.DRAGGING.friction); + int newVelocity = (int) ((maxWidth - activeHorizontalSpring.getCurrentValue() - manager.getConfig().getHeadWidth()) * SpringConfigsHolder.DRAGGING.friction); if (newVelocity > xVelocity) xVelocity = (newVelocity); } @@ -251,7 +255,7 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci activeVerticalSpring.setVelocity(yVelocity); if (!wasDragging) { - boolean handled = container.onItemSelected(activeChatHead); + boolean handled = manager.onItemSelected(activeChatHead); if (!handled) { deactivate(); return false; @@ -262,7 +266,7 @@ public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVeloci private void deactivate() { Bundle bundle = getBundleWithHero(); - container.setArrangement(MaximizedArrangement.class, bundle); + manager.setArrangement(MaximizedArrangement.class, bundle); } @NonNull @@ -286,7 +290,7 @@ public Integer getHeroIndex() { private Integer getHeroIndex(ChatHead hero) { int heroIndex = 0; - List> chatHeads = container.getChatHeads(); + List> chatHeads = manager.getChatHeads(); int i = 0; for (ChatHead chatHead : chatHeads) { if (hero == chatHead) { @@ -314,9 +318,9 @@ public boolean canDrag(ChatHead chatHead) { @Override public void removeOldestChatHead() { - for (ChatHead chatHead : container.getChatHeads()) { + for (ChatHead chatHead : manager.getChatHeads()) { if (!chatHead.isSticky()) { - container.removeChatHead(chatHead.getKey(), false); + manager.removeChatHead(chatHead.getKey(), false); break; } } @@ -336,10 +340,10 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW if (spring == activeHorizontalSpring) { double xPosition = activeHorizontalSpring.getCurrentValue(); - if (xPosition + container.getConfig().getHeadWidth() > maxWidth && activeHorizontalSpring.getVelocity() > 0) { + 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 - container.getConfig().getHeadWidth(); + int newPos = maxWidth - manager.getConfig().getHeadWidth(); activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); activeHorizontalSpring.setEndValue(newPos); } else if (xPosition < 0 && activeHorizontalSpring.getVelocity() < 0) { @@ -355,12 +359,12 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW } } else if (spring == activeVerticalSpring) { double yPosition = activeVerticalSpring.getCurrentValue(); - if (yPosition + container.getConfig().getHeadWidth() > maxHeight && activeVerticalSpring.getVelocity() > 0) { + 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 - container.getConfig().getHeadHeight()); + 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); @@ -379,8 +383,8 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW /** Capturing check **/ - int[] coords = container.getChatHeadCoordsForCloseButton(activeChatHead); - double distanceCloseButtonFromHead = container.getDistanceCloseButtonFromHead((float) activeHorizontalSpring.getCurrentValue() + container.getConfig().getHeadWidth() / 2, (float) activeVerticalSpring.getCurrentValue() + container.getConfig().getHeadHeight() / 2); + 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); @@ -398,13 +402,13 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW } if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeVerticalSpring.isAtRest()) { - container.getCloseButton().disappear(false, true); - container.captureChatHeads(activeChatHead); + manager.getCloseButton().disappear(false, true); + manager.captureChatHeads(activeChatHead); } if (!activeVerticalSpring.isAtRest()) { - container.getCloseButton().appear(); + manager.getCloseButton().appear(); } else { - container.getCloseButton().disappear(true, true); + manager.getCloseButton().disappear(true, true); } } @@ -414,7 +418,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW @Override public void bringToFront(ChatHead chatHead) { Bundle b = getBundle(getHeroIndex(chatHead)); - onActivate(container, b, container.getMaxWidth(), container.getMaxHeight(), true); + onActivate(manager, b, manager.getMaxWidth(), manager.getMaxHeight(), true); } @Override diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java new file mode 100755 index 0000000..49e9ace --- /dev/null +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java @@ -0,0 +1,714 @@ +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.support.v4.app.FragmentTransaction; +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.CircularArrangement; +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 implements ChatHeadCloseButton.CloseButtonListener, ChatHeadManager { + + private static final int OVERLAY_TRANSITION_DURATION = 200; + private final Map, ChatHeadArrangement> arrangements = new HashMap<>(3); + private final Context context; + private final ChatHeadContainer chatHeadContainer; + private List> chatHeads; + private int maxWidth; + private int maxHeight; + private ChatHeadCloseButton closeButton; + private ChatHeadArrangement activeArrangement; + private ChatHeadViewAdapter viewAdapter; + private ChatHeadOverlayView overlayView; + private OnItemSelectedListener 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> 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 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 Fragment getFragment(T key, boolean createIfRequired) { + return getFragment(findChatHeadByKey(key), createIfRequired); + } + + + @Override + public void onMeasure() { + maxHeight = chatHeadContainer.getContainerHeight(); + maxWidth = chatHeadContainer.getContainerWidth(); + if (maxHeight > 0 && maxWidth > 0) { + if (requestedArrangement != null) setArrangementImpl(requestedArrangement); + requestedArrangement = null; + } + } + +// @Override +// public boolean dispatchTouchEvent(MotionEvent ev) { +// if (activeArrangement != null) { +// activeArrangement.handleRawTouchEvent(ev); +// } +// return super.dispatchTouchEvent(ev); +// } + + @Override + public ChatHead addChatHead(T key, boolean isSticky, boolean animated) { + ChatHead chatHead = findChatHeadByKey(key); + if (chatHead == null) { + chatHead = new ChatHead(this, springSystem, getContext(), isSticky); + chatHead.setKey(key); + chatHeads.add(chatHead); + ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getHeadWidth(), getConfig().getHeadHeight(), Gravity.LEFT | 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 findChatHeadByKey(T key) { + for (ChatHead 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> iterator = chatHeads.iterator(); iterator.hasNext(); ) { + ChatHead 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) { + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(metrics); + this.displayMetrics = metrics; + setConfig(chatHeadDefaultConfig); + chatHeads = new ArrayList<>(5); + arrowLayout = new UpArrowLayout(context); + arrowLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + arrowLayout.setVisibility(View.GONE); + springSystem = SpringSystem.create(); + closeButton = new ChatHeadCloseButton(context, this, maxHeight, maxWidth); + ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getCloseButtonHeight(), getConfig().getCloseButtonWidth(), Gravity.TOP|Gravity.LEFT, 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(this)); + arrangements.put(CircularArrangement.class, new CircularArrangement(this)); + setupOverlay(context); + 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); + //viewManager.addView(overlayView, overlayView.getLayoutParams()); + } + + 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 - 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 arrangementType) { + return arrangements.get(arrangementType); + } + + @Override + public void setArrangement(final Class arrangement, Bundle extras) { + setArrangement(arrangement, extras, true); + } + + @Override + public void setArrangement(final Class arrangement, Bundle extras, boolean animated) { + this.requestedArrangement = new ArrangementChangeRequest(arrangement, extras, animated); + onMeasure(); + } + + /** + * Should only be called after onMeasure + * + * @param requestedArrangement + */ + private void setArrangementImpl(ArrangementChangeRequest requestedArrangement) { + ChatHeadArrangement chatHeadArrangement = arrangements.get(requestedArrangement.getArrangement()); + ChatHeadArrangement oldArrangement = null; + ChatHeadArrangement newArrangement = chatHeadArrangement; + Bundle extras = requestedArrangement.getExtras(); + if (extras == null) extras = new Bundle(); + + if (activeArrangement != null && chatHeadArrangement != activeArrangement) { + extras.putAll(activeArrangement.getRetainBundle()); + activeArrangement.onDeactivate(maxWidth, maxHeight); + oldArrangement = activeArrangement; + } + activeArrangement = chatHeadArrangement; + activeArrangementBundle = extras; + chatHeadArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangement.isAnimated()); + 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 onItemSelectedListener) { + this.itemSelectedListener = onItemSelectedListener; + } + + @Override + public boolean onItemSelected(ChatHead chatHead) { + return itemSelectedListener != null && itemSelectedListener.onChatHeadSelected(chatHead.getKey(), chatHead); + } + + @Override + public void onItemRollOver(ChatHead chatHead) { + if (itemSelectedListener != null) + itemSelectedListener.onChatHeadRollOver(chatHead.getKey(), chatHead); + } + + @Override + public void onItemRollOut(ChatHead 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() { + closeButtonShadow.setVisibility(View.VISIBLE); + } + + @Override + public void onCloseButtonDisappear() { + closeButtonShadow.setVisibility(View.GONE); + } + + + @Override + public void reloadFragment(T key) { + removeFragment(findChatHeadByKey(key)); + if (activeArrangement != null) { + activeArrangement.onReloadFragment(findChatHeadByKey(key)); + } + } + + @Override + public SpringSystem getSpringSystem() { + return springSystem; + } + + @Override + public Fragment addFragment(ChatHead activeChatHead, ViewGroup parent) { + try { + FragmentManager manager = getFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + + Fragment fragment = getFragmentManager().findFragmentByTag(activeChatHead.getKey().toString()); + + if (fragment == null) { + fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); + transaction.add(parent.getId(), fragment, activeChatHead.getKey().toString()); + } else { + if (fragment.isDetached()) { + transaction.attach(fragment); + } + } + if (fragment != currentFragment && currentFragment != null) { + transaction.detach(currentFragment); + } + currentFragment = fragment; + transaction.commitAllowingStateLoss(); + manager.executePendingTransactions(); + return fragment; + } catch (IllegalStateException ex) { + //raised when activity has been destroyed + ex.printStackTrace(); + } + return null; + } + + @Override + public Fragment removeFragment(ChatHead chatHead) { + try { + FragmentManager manager = getFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + Fragment fragment = getFragment(chatHead, false); + if (fragment == null) { + //we dont have it in our cache. So we create it and add it + } else { + //we have added it already sometime earlier. So re-attach it. + transaction.remove(fragment); + + } + if (fragment == currentFragment) { + currentFragment = null; + } + transaction.commitAllowingStateLoss(); + manager.executePendingTransactions(); + return fragment; + } catch (IllegalStateException ex) { + //raised when activity has been destroyed + ex.printStackTrace(); + } + return null; + } + + @Override + public Fragment detachFragment(ChatHead chatHead) { + try { + FragmentManager fragmentManager = getFragmentManager(); + Fragment fragment = getFragment(chatHead, false); + if (fragment != null) { + + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + if (!fragment.isDetached()) { + fragmentTransaction.detach(fragment); + } + fragmentTransaction.commitAllowingStateLoss(); + } + return fragment; + } catch (IllegalStateException ex) { + //raised when activity has been destroyed + ex.printStackTrace(); + } + return null; + } + + + Fragment getFragment(ChatHead activeChatHead, boolean createIfRequired) { + String tag = ""; + if (activeChatHead != null) { + tag = activeChatHead.getKey().toString(); + } + Fragment fragment = getFragmentManager().findFragmentByTag(tag); + if (fragment == null && createIfRequired) { + fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); + } + return fragment; + } + + + @Override + public FragmentManager getFragmentManager() { + if (fragmentManager == null) { + if (getViewAdapter() == null) + throw new IllegalStateException(ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); + fragmentManager = getViewAdapter().getFragmentManager(); + if (fragmentManager == null) + throw new IllegalStateException(FragmentManager.class.getSimpleName() + " returned from " + ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); + } + return fragmentManager; + } + + @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); + } + for (Map.Entry, 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 chatHeadState = new LinkedHashMap<>(); + for (ChatHead 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 chatHeads = savedState.getChatHeads(); + for (Map.Entry 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 CREATOR = new Parcelable.Creator() { + + @Override + public SavedState createFromParcel(Parcel source) { + return new SavedState(source); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + private Class activeArrangement; + private Bundle activeArrangementBundle; + private LinkedHashMap chatHeads; + + public SavedState(Parcel source) { + super(source); + activeArrangement = (Class) source.readSerializable(); + activeArrangementBundle = source.readBundle(); + chatHeads = (LinkedHashMap) source.readSerializable(); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + public Class getActiveArrangement() { + return activeArrangement; + } + + public void setActiveArrangement(Class 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 getChatHeads() { + return chatHeads; + } + + public void setChatHeads(LinkedHashMap chatHeads) { + this.chatHeads = chatHeads; + } + } + + private class ArrangementChangeRequest { + private final Bundle extras; + private final Class arrangement; + private final boolean animated; + + public ArrangementChangeRequest(Class arrangement, Bundle extras, boolean animated) { + this.arrangement = arrangement; + this.extras = extras; + this.animated = animated; + } + + public Bundle getExtras() { + return extras; + } + + public Class getArrangement() { + return arrangement; + } + + public boolean isAnimated() { + return animated; + } + } +} diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java new file mode 100644 index 0000000..9de2ee2 --- /dev/null +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -0,0 +1,152 @@ +package com.flipkart.chatheads.ui.container; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; + +import com.flipkart.chatheads.ui.FrameChatHeadContainer; + +import static android.content.Context.WINDOW_SERVICE; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + +/** + * Created by kiran.kumar on 08/11/16. + */ + +public class WindowManagerContainer extends FrameChatHeadContainer { + private final View motionCaptureView; + private int cachedHeight; + private int cachedWidth; + private WindowManager windowManager; + + public WindowManagerContainer(Context context) { + super(context); + motionCaptureView = new View(context); + motionCaptureView.setOnTouchListener(new MotionCapturingTouchListener()); + addContainer(motionCaptureView, true); + } + + public WindowManager getWindowManager() { + if (windowManager == null) { + windowManager = (WindowManager) getContext().getSystemService(WINDOW_SERVICE); + } + return windowManager; + } + + + + @Override + public int getContainerHeight() { + if (cachedHeight <= 0) { + cachedHeight = windowManager.getDefaultDisplay().getHeight(); + } + return cachedHeight; + } + + @Override + public int getContainerWidth() { + if (cachedWidth <= 0) { + cachedWidth = windowManager.getDefaultDisplay().getWidth(); + } + return cachedWidth; + } + + private void setContainerHeight(View container, int height) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + layoutParams.height = height; + getWindowManager().updateViewLayout(container, layoutParams); + } + + private void setContainerWidth(View container, int width) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + layoutParams.width = width; + getWindowManager().updateViewLayout(container, layoutParams); + } + + private WindowManager.LayoutParams getOrCreateLayoutParamsForContainer(View container) { + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) container.getLayoutParams(); + if (layoutParams == null) { + layoutParams = createContainerLayoutParams(false); + container.setLayoutParams(layoutParams); + } + return layoutParams; + } + + protected void setContainerX(View container, int xPosition) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + layoutParams.x = xPosition; + getWindowManager().updateViewLayout(container, layoutParams); + } + + protected int getContainerX(View container) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + return layoutParams.x; + } + + + protected void setContainerY(View container, int yPosition) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + layoutParams.y = yPosition; + getWindowManager().updateViewLayout(container, layoutParams); + } + + protected int getContainerY(View container) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); + return layoutParams.y; + } + + private WindowManager.LayoutParams createContainerLayoutParams(boolean focusable) { + int focusableFlag = FLAG_NOT_TOUCH_MODAL; + if (!focusable) { + focusableFlag |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, + WindowManager.LayoutParams.TYPE_PHONE, + focusableFlag | FLAG_LAYOUT_NO_LIMITS, + PixelFormat.TRANSLUCENT); + layoutParams.x = 0; + layoutParams.y = 0; + layoutParams.gravity = Gravity.TOP | Gravity.START; + return layoutParams; + } + + @Override + public void addContainer(View container, boolean focusable) { + WindowManager.LayoutParams containerLayoutParams = createContainerLayoutParams(focusable); + container.setLayoutParams(containerLayoutParams); + getWindowManager().addView(container, containerLayoutParams); + } + + @Override + public void setViewX(View view, int xPosition, boolean isHero) { + super.setViewX(view, xPosition, isHero); + if (isHero) { + setContainerX(motionCaptureView, xPosition); + setContainerWidth(motionCaptureView, view.getMeasuredWidth()); + } + } + + @Override + public void setViewY(View view, int yPosition, boolean isHero) { + super.setViewY(view, yPosition, isHero); + if (isHero) { + setContainerY(motionCaptureView, yPosition); + setContainerHeight(motionCaptureView, view.getMeasuredHeight()); + } + } + + private class MotionCapturingTouchListener implements View.OnTouchListener { + @Override + public boolean onTouch(View v, MotionEvent event) { + event.offsetLocation(getContainerX(v), getContainerY(v)); + getFrameLayout().dispatchTouchEvent(event); + return false; + } + } +} diff --git a/library/src/main/res/layout/arrow_layout.xml b/library/src/main/res/layout/arrow_layout.xml deleted file mode 100644 index 6943c7c..0000000 --- a/library/src/main/res/layout/arrow_layout.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file From 58a2537026ca801a01ddae94ea1aee0ba505d0c7 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 8 Nov 2016 17:00:26 +0530 Subject: [PATCH 02/14] Maximized arrangement now works in a service --- README.md | 2 +- .../springyheads/demo/ChatHeadService.java | 43 +++---- demo/src/main/res/layout/fragment_test.xml | 4 +- .../com/flipkart/chatheads/ui/ChatHead.java | 4 +- .../chatheads/ui/ChatHeadCloseButton.java | 4 +- .../chatheads/ui/ChatHeadContainer.java | 6 +- .../chatheads/ui/ChatHeadDefaultConfig.java | 2 +- .../chatheads/ui/ChatHeadManager.java | 21 +-- .../chatheads/ui/ChatHeadViewAdapter.java | 10 +- .../chatheads/ui/FrameChatHeadContainer.java | 8 +- .../chatheads/ui/MaximizedArrangement.java | 9 +- .../chatheads/ui/MinimizedArrangement.java | 2 +- .../ui/container/DefaultChatHeadManager.java | 120 +++--------------- .../ui/container/WindowManagerContainer.java | 64 +++++++--- 14 files changed, 105 insertions(+), 194 deletions(-) diff --git a/README.md b/README.md index 203416c..73b6f30 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ chatContainer.setViewAdapter(new ChatHeadViewAdapter() { } @Override - public Fragment instantiateFragment(Object key, ChatHead chatHead) { + public Fragment createView(Object key, ChatHead chatHead) { // return the fragment which should be shown when the arrangment switches to maximized (on clicking a chat head) // you can use the key parameter to get back the object you passed in the addChatHead method. // this key should be used to decide which fragment to show. diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java index 688a797..7a2204c 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java @@ -1,35 +1,27 @@ package com.flipkart.springyheads.demo; import android.app.Notification; -import android.app.PendingIntent; import android.app.Service; import android.content.Intent; -import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.os.IBinder; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.support.v4.app.NotificationCompat; -import android.util.DisplayMetrics; -import android.view.Gravity; +import android.view.LayoutInflater; import android.view.View; -import android.view.WindowManager; +import android.view.ViewGroup; +import android.widget.TextView; import com.flipkart.chatheads.ui.ChatHead; +import com.flipkart.chatheads.ui.ChatHeadContainer; import com.flipkart.chatheads.ui.ChatHeadViewAdapter; -import com.flipkart.chatheads.ui.FrameChatHeadContainer; import com.flipkart.chatheads.ui.MinimizedArrangement; -import com.flipkart.chatheads.ui.ChatHeadContainer; import com.flipkart.chatheads.ui.container.DefaultChatHeadManager; import com.flipkart.chatheads.ui.container.WindowManagerContainer; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - public class ChatHeadService extends Service { private DefaultChatHeadManager chatContainer; + private int chatHeadIdentifier = 0; @Override @@ -42,24 +34,21 @@ public IBinder onBind(Intent intent) { public void onCreate() { super.onCreate(); - final WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); ChatHeadContainer chatHeadContainer = new WindowManagerContainer(this); - chatContainer = new DefaultChatHeadManager(this, chatHeadContainer); + chatContainer.setViewAdapter(new ChatHeadViewAdapter() { - chatContainer.setViewAdapter(new ChatHeadViewAdapter() { @Override - public FragmentManager getFragmentManager() { - return null; + public View createView(String key, ChatHead chatHead, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.fragment_test, parent, false); + TextView identifier = (TextView) view.findViewById(R.id.identifier); + identifier.setText(key); + return view; } @Override - public Fragment instantiateFragment(Object key, ChatHead chatHead) { - return TestFragment.newInstance(0); - } - - @Override - public Drawable getChatHeadDrawable(Object key) { + public Drawable getChatHeadDrawable(String key) { return getResources().getDrawable(R.drawable.head); } @@ -69,12 +58,11 @@ public Drawable getPointerDrawable() { } @Override - public View getTitleView(Object key, ChatHead chatHead) { + public View getTitleView(String key, ChatHead chatHead) { return null; } }); - addChatHead(); addChatHead(); addChatHead(); @@ -96,7 +84,8 @@ private void moveToForeground() { } private void addChatHead() { - chatContainer.addChatHead("head" + Math.random(), false, true); + chatHeadIdentifier++; + chatContainer.addChatHead(String.valueOf(chatHeadIdentifier), false, true); } @Override diff --git a/demo/src/main/res/layout/fragment_test.xml b/demo/src/main/res/layout/fragment_test.xml index cc66bdc..3563d05 100755 --- a/demo/src/main/res/layout/fragment_test.xml +++ b/demo/src/main/res/layout/fragment_test.xml @@ -3,13 +3,13 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/rounded_white" - android:orientation="vertical" - tools:context="com.flipkart.chatheads.demo.TestFragment"> + android:orientation="vertical"> diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java index f57c2c6..60e37f9 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHead.java @@ -106,7 +106,7 @@ private void init() { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - manager.getChatHeadContainer().setViewX(ChatHead.this, (int)spring.getCurrentValue(), isHero); + manager.getChatHeadContainer().setViewX(ChatHead.this, (int)spring.getCurrentValue()); } @Override @@ -122,7 +122,7 @@ public void onSpringAtRest(Spring spring) { @Override public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); - manager.getChatHeadContainer().setViewY(ChatHead.this, (int)spring.getCurrentValue(), isHero); + manager.getChatHeadContainer().setViewY(ChatHead.this, (int)spring.getCurrentValue()); } @Override diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java index adf6e4e..c523937 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java @@ -51,7 +51,7 @@ private void init(final ChatHeadManager manager, int maxHeight, int maxWidth) { public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); int x = centerX + (int) spring.getCurrentValue() - getMeasuredWidth()/2; - manager.getChatHeadContainer().setViewX(ChatHeadCloseButton.this, x, false); + manager.getChatHeadContainer().setViewX(ChatHeadCloseButton.this, x); // System.out.println("spring x = [" + x + "] center "+centerX); } @@ -62,7 +62,7 @@ public void onSpringUpdate(Spring spring) { public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); int y = centerY + (int) spring.getCurrentValue() - getMeasuredHeight()/2; - manager.getChatHeadContainer().setViewY(ChatHeadCloseButton.this, y, false); + manager.getChatHeadContainer().setViewY(ChatHeadCloseButton.this, y); // System.out.println("spring y = [" + y + "] center "+centerY); } }); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java index 3abdb88..a56f2a9 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java @@ -23,9 +23,9 @@ public interface ChatHeadContainer { int getContainerWidth(); - void setViewX(View view, int xPosition, boolean isHero); + void setViewX(View view, int xPosition); - void setViewY(View view, int yPosition, boolean isHero); + void setViewY(View view, int yPosition); int getViewX(View view); @@ -36,4 +36,6 @@ public interface ChatHeadContainer { void addView(View view, ViewGroup.LayoutParams layoutParams); void removeView(View view); + + void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArrangement newArrangement); } \ No newline at end of file diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java index 1b82bf0..35d1b06 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java @@ -15,7 +15,7 @@ public ChatHeadDefaultConfig(Context context) { setHeadWidth(ChatHeadUtils.dpToPx(context, diameter)); setHeadHorizontalSpacing(ChatHeadUtils.dpToPx(context, 10)); setHeadVerticalSpacing(ChatHeadUtils.dpToPx(context, 5)); - setInitialPosition(new Point(0,ChatHeadUtils.dpToPx(context,200))); + setInitialPosition(new Point(0,ChatHeadUtils.dpToPx(context,0))); setCloseButtonWidth(ChatHeadUtils.dpToPx(context, 62)); setCloseButtonHeight(ChatHeadUtils.dpToPx(context, 62)); setCloseButtonBottomMargin(ChatHeadUtils.dpToPx(context, 50)); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java index 162b725..4e72b7e 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadManager.java @@ -6,6 +6,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.util.DisplayMetrics; +import android.view.View; import android.view.ViewGroup; import com.facebook.rebound.SpringSystem; @@ -42,16 +43,6 @@ public interface ChatHeadManager { void selectChatHead(ChatHead chatHead); void selectChatHead(T key); - - /** - * Returns the fragment for the key if its already present. If createIfRequired is set to true, it will create and return it. - * - * @param key - * @param createIfRequired - * @return - */ - Fragment getFragment(T key, boolean createIfRequired); - /** * Should be called when measuring of the container is done. * Typically called from onMeasure or onLayout @@ -108,17 +99,15 @@ public interface ChatHeadManager { void onCloseButtonDisappear(); - void reloadFragment(T key); + void recreateView(T key); SpringSystem getSpringSystem(); - Fragment addFragment(ChatHead activeChatHead, ViewGroup parent); - - Fragment removeFragment(ChatHead chatHead); + View addView(ChatHead activeChatHead, ViewGroup parent); - Fragment detachFragment(ChatHead chatHead); + View removeView(ChatHead chatHead); - FragmentManager getFragmentManager(); + View detachView(ChatHead chatHead); ChatHeadConfig getConfig(); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java index 2c1e2ef..13765b6 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java @@ -4,6 +4,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.view.View; +import android.view.ViewGroup; import java.io.Serializable; @@ -13,14 +14,9 @@ public interface ChatHeadViewAdapter { /** - * Return the fragment manager. This manager will be asked once and used throughout chat heads library. + * Based on the key, this should instantiate and return a View. This View will be removed when the chathead is removed. */ - public FragmentManager getFragmentManager(); - - /** - * Based on the key, this should instantiate and return a fragment. This fragment will be removed when the chathead is removed. - */ - public Fragment instantiateFragment(T key, ChatHead chatHead); + public View createView(T key, ChatHead chatHead, ViewGroup parent); /** * Should return the view used to represent a chat "head". Typically a rounded imageview. diff --git a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java index c025905..86e77b9 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java @@ -1,16 +1,12 @@ package com.flipkart.chatheads.ui; import android.content.Context; -import android.graphics.Color; import android.util.DisplayMetrics; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; -import com.facebook.rebound.Spring; - /** * Created by kiran.kumar on 02/11/16. */ @@ -55,12 +51,12 @@ public ViewGroup.LayoutParams createLayoutParams(int height, int width, int grav } @Override - public void setViewX(View view, int xPosition, boolean isHero) { + public void setViewX(View view, int xPosition) { view.setTranslationX(xPosition); } @Override - public void setViewY(View view, int yPosition, boolean isHero) { + public void setViewY(View view, int yPosition) { view.setTranslationY(yPosition); } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java index 78ddf6f..d5b03d5 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java @@ -102,7 +102,7 @@ public void onClick(View v) { @Override public void onDeactivate(int maxWidth, int maxHeight) { if (currentChatHead != null) { - manager.detachFragment(currentChatHead); + manager.detachView(currentChatHead); } hideView(); manager.hideOverlayView(true); @@ -288,7 +288,8 @@ private void showView(ChatHead activeChatHead, double dx, double dy, double dist private void pointTo(ChatHead activeChatHead) { UpArrowLayout arrowLayout = getArrowLayout(); - manager.addFragment(activeChatHead, arrowLayout); + manager.addView(activeChatHead, arrowLayout); + arrowLayout.bringToFront(); Point point = positions.get(activeChatHead); if (point != null) { int padding = manager.getConfig().getHeadVerticalSpacing(maxWidth, maxHeight); @@ -310,7 +311,7 @@ public void onChatHeadAdded(final ChatHead chatHead, final boolean animated) { @Override public void onChatHeadRemoved(ChatHead removed) { - manager.removeFragment(removed); + manager.removeView(removed); positions.remove(removed); boolean isEmpty = false; if (currentChatHead == removed) { @@ -421,7 +422,7 @@ public void bringToFront(final ChatHead chatHead) { @Override public void onReloadFragment(ChatHead chatHead) { if (currentChatHead != null && chatHead == currentChatHead) { - manager.addFragment(chatHead, getArrowLayout()); + manager.addView(chatHead, getArrowLayout()); } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java index 6846017..7e66acd 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java @@ -171,7 +171,7 @@ public void onChatHeadAdded(ChatHead chatHead, boolean animated) { @Override public void onChatHeadRemoved(ChatHead removed) { - manager.removeFragment(removed); + manager.removeView(removed); if (removed == hero) { hero = null; } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java index 49e9ace..7edea3f 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java @@ -10,7 +10,6 @@ import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; @@ -71,6 +70,7 @@ public class DefaultChatHeadManager implements ChatHeadC private ArrangementChangeRequest requestedArrangement; private DisplayMetrics displayMetrics; private UpArrowLayout arrowLayout; + public DefaultChatHeadManager(Context context, ChatHeadContainer chatHeadContainer) { this.context = context; this.chatHeadContainer = chatHeadContainer; @@ -165,11 +165,6 @@ public void selectChatHead(T key) { } } - @Override - public Fragment getFragment(T key, boolean createIfRequired) { - return getFragment(findChatHeadByKey(key), createIfRequired); - } - @Override public void onMeasure() { @@ -281,10 +276,11 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { 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(getConfig().getCloseButtonHeight(), getConfig().getCloseButtonWidth(), Gravity.TOP|Gravity.LEFT, 0); + ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getCloseButtonHeight(), getConfig().getCloseButtonWidth(), Gravity.TOP | Gravity.LEFT, 0); closeButton.setListener(this); chatHeadContainer.addView(closeButton, layoutParams); closeButtonShadow = new ImageView(getContext()); @@ -301,9 +297,10 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { } private void setupOverlay(Context context) { - //overlayView = new ChatHeadOverlayView(context); - //overlayView.setBackgroundResource(R.drawable.overlay_transition); - //viewManager.addView(overlayView, overlayView.getLayoutParams()); + 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) { @@ -366,7 +363,9 @@ private void setArrangementImpl(ArrangementChangeRequest requestedArrangement) { activeArrangement = chatHeadArrangement; activeArrangementBundle = extras; chatHeadArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangement.isAnimated()); + chatHeadContainer.onArrangementChanged(oldArrangement, newArrangement); if (listener != null) listener.onChatHeadArrangementChanged(oldArrangement, newArrangement); + } @Override @@ -444,8 +443,8 @@ public void onCloseButtonDisappear() { @Override - public void reloadFragment(T key) { - removeFragment(findChatHeadByKey(key)); + public void recreateView(T key) { + removeView(findChatHeadByKey(key)); if (activeArrangement != null) { activeArrangement.onReloadFragment(findChatHeadByKey(key)); } @@ -457,108 +456,23 @@ public SpringSystem getSpringSystem() { } @Override - public Fragment addFragment(ChatHead activeChatHead, ViewGroup parent) { - try { - FragmentManager manager = getFragmentManager(); - FragmentTransaction transaction = manager.beginTransaction(); - - Fragment fragment = getFragmentManager().findFragmentByTag(activeChatHead.getKey().toString()); - - if (fragment == null) { - fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); - transaction.add(parent.getId(), fragment, activeChatHead.getKey().toString()); - } else { - if (fragment.isDetached()) { - transaction.attach(fragment); - } - } - if (fragment != currentFragment && currentFragment != null) { - transaction.detach(currentFragment); - } - currentFragment = fragment; - transaction.commitAllowingStateLoss(); - manager.executePendingTransactions(); - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } - return null; + public View addView(ChatHead activeChatHead, ViewGroup parent) { + View view = viewAdapter.createView(activeChatHead.getKey(), activeChatHead, parent); + parent.addView(view); + return view; } @Override - public Fragment removeFragment(ChatHead chatHead) { - try { - FragmentManager manager = getFragmentManager(); - FragmentTransaction transaction = manager.beginTransaction(); - Fragment fragment = getFragment(chatHead, false); - if (fragment == null) { - //we dont have it in our cache. So we create it and add it - } else { - //we have added it already sometime earlier. So re-attach it. - transaction.remove(fragment); - - } - if (fragment == currentFragment) { - currentFragment = null; - } - transaction.commitAllowingStateLoss(); - manager.executePendingTransactions(); - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } + public View removeView(ChatHead chatHead) { return null; } @Override - public Fragment detachFragment(ChatHead chatHead) { - try { - FragmentManager fragmentManager = getFragmentManager(); - Fragment fragment = getFragment(chatHead, false); - if (fragment != null) { - - FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - if (!fragment.isDetached()) { - fragmentTransaction.detach(fragment); - } - fragmentTransaction.commitAllowingStateLoss(); - } - return fragment; - } catch (IllegalStateException ex) { - //raised when activity has been destroyed - ex.printStackTrace(); - } + public View detachView(ChatHead chatHead) { return null; } - Fragment getFragment(ChatHead activeChatHead, boolean createIfRequired) { - String tag = ""; - if (activeChatHead != null) { - tag = activeChatHead.getKey().toString(); - } - Fragment fragment = getFragmentManager().findFragmentByTag(tag); - if (fragment == null && createIfRequired) { - fragment = getViewAdapter().instantiateFragment(activeChatHead.getKey(), activeChatHead); - } - return fragment; - } - - - @Override - public FragmentManager getFragmentManager() { - if (fragmentManager == null) { - if (getViewAdapter() == null) - throw new IllegalStateException(ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); - fragmentManager = getViewAdapter().getFragmentManager(); - if (fragmentManager == null) - throw new IllegalStateException(FragmentManager.class.getSimpleName() + " returned from " + ChatHeadViewAdapter.class.getSimpleName() + " should not be null"); - } - return fragmentManager; - } - @Override public ChatHeadConfig getConfig() { return config; diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java index 9de2ee2..d39b242 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -2,17 +2,19 @@ import android.content.Context; import android.graphics.PixelFormat; -import android.util.DisplayMetrics; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; +import com.flipkart.chatheads.ui.ChatHead; +import com.flipkart.chatheads.ui.ChatHeadArrangement; import com.flipkart.chatheads.ui.FrameChatHeadContainer; +import com.flipkart.chatheads.ui.MaximizedArrangement; +import com.flipkart.chatheads.ui.MinimizedArrangement; import static android.content.Context.WINDOW_SERVICE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; /** @@ -20,10 +22,17 @@ */ public class WindowManagerContainer extends FrameChatHeadContainer { + /** + * A transparent view of the size of chat head which capture motion events and delegates them to the real view (frame layout) + * This view is required since window managers will delegate the touch events to the window beneath it only if they are outside the bounds. + * {@link android.view.WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL} + */ private final View motionCaptureView; + private int cachedHeight; private int cachedWidth; private WindowManager windowManager; + private ChatHeadArrangement currentArrangement; public WindowManagerContainer(Context context) { super(context); @@ -39,8 +48,6 @@ public WindowManager getWindowManager() { return windowManager; } - - @Override public int getContainerHeight() { if (cachedHeight <= 0) { @@ -57,19 +64,19 @@ public int getContainerWidth() { return cachedWidth; } - private void setContainerHeight(View container, int height) { + protected void setContainerHeight(View container, int height) { WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); layoutParams.height = height; getWindowManager().updateViewLayout(container, layoutParams); } - private void setContainerWidth(View container, int width) { + protected void setContainerWidth(View container, int width) { WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); layoutParams.width = width; getWindowManager().updateViewLayout(container, layoutParams); } - private WindowManager.LayoutParams getOrCreateLayoutParamsForContainer(View container) { + protected WindowManager.LayoutParams getOrCreateLayoutParamsForContainer(View container) { WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) container.getLayoutParams(); if (layoutParams == null) { layoutParams = createContainerLayoutParams(false); @@ -101,14 +108,14 @@ protected int getContainerY(View container) { return layoutParams.y; } - private WindowManager.LayoutParams createContainerLayoutParams(boolean focusable) { + protected WindowManager.LayoutParams createContainerLayoutParams(boolean focusable) { int focusableFlag = FLAG_NOT_TOUCH_MODAL; if (!focusable) { focusableFlag |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, WindowManager.LayoutParams.TYPE_PHONE, - focusableFlag | FLAG_LAYOUT_NO_LIMITS, + focusableFlag, PixelFormat.TRANSLUCENT); layoutParams.x = 0; layoutParams.y = 0; @@ -124,24 +131,41 @@ public void addContainer(View container, boolean focusable) { } @Override - public void setViewX(View view, int xPosition, boolean isHero) { - super.setViewX(view, xPosition, isHero); - if (isHero) { - setContainerX(motionCaptureView, xPosition); - setContainerWidth(motionCaptureView, view.getMeasuredWidth()); + public void setViewX(View view, int xPosition) { + super.setViewX(view, xPosition); + if (view instanceof ChatHead) { + boolean hero = ((ChatHead) view).isHero(); + if (hero && currentArrangement instanceof MinimizedArrangement) { + setContainerX(motionCaptureView, xPosition); + setContainerWidth(motionCaptureView, view.getMeasuredWidth()); + } + } + } + + @Override + public void setViewY(View view, int yPosition) { + super.setViewY(view, yPosition); + if (view instanceof ChatHead && currentArrangement instanceof MinimizedArrangement) { + boolean hero = ((ChatHead) view).isHero(); + if (hero) { + setContainerY(motionCaptureView, yPosition); + setContainerHeight(motionCaptureView, view.getMeasuredHeight()); + } } } @Override - public void setViewY(View view, int yPosition, boolean isHero) { - super.setViewY(view, yPosition, isHero); - if (isHero) { - setContainerY(motionCaptureView, yPosition); - setContainerHeight(motionCaptureView, view.getMeasuredHeight()); + public void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArrangement newArrangement) { + currentArrangement = newArrangement; + if (oldArrangement instanceof MinimizedArrangement && newArrangement instanceof MaximizedArrangement) { + setContainerX(motionCaptureView, 0); + setContainerY(motionCaptureView, 0); + setContainerWidth(motionCaptureView, getContainerWidth()); + setContainerHeight(motionCaptureView, getContainerHeight()); } } - private class MotionCapturingTouchListener implements View.OnTouchListener { + protected class MotionCapturingTouchListener implements View.OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { event.offsetLocation(getContainerX(v), getContainerY(v)); From cd8e70c42c62c7afad51c53e2c62ef7a3a4f486c Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 8 Nov 2016 17:44:06 +0530 Subject: [PATCH 03/14] 1.Added support for hiding close button. 2.Minimize arrangement on home button and app switch button --- .../flipkart/chatheads/ui/ChatHeadConfig.java | 9 +++++ .../chatheads/ui/ChatHeadContainer.java | 3 ++ .../chatheads/ui/ChatHeadDefaultConfig.java | 1 + .../chatheads/ui/FrameChatHeadContainer.java | 10 ++++++ .../ui/container/DefaultChatHeadManager.java | 19 +++++++--- .../ui/container/WindowManagerContainer.java | 35 +++++++++++++++++-- 6 files changed, 71 insertions(+), 6 deletions(-) diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadConfig.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadConfig.java index 7a676da..3787cec 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadConfig.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadConfig.java @@ -18,6 +18,15 @@ public class ChatHeadConfig { private int circularRingWidth; private int circularRingHeight; private int circularFanOutRadius; + private boolean closeButtonHidden; + + public boolean isCloseButtonHidden() { + return closeButtonHidden; + } + + public void setCloseButtonHidden(boolean closeButtonHidden) { + this.closeButtonHidden = closeButtonHidden; + } public void setCircularFanOutRadius(int circularFanOutRadius) { this.circularFanOutRadius = circularFanOutRadius; diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java index a56f2a9..dcd1c57 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadContainer.java @@ -15,6 +15,9 @@ * Created by kiran.kumar on 27/10/16. */ public interface ChatHeadContainer { + + void onInitialized(ChatHeadManager manager); + DisplayMetrics getDisplayMetrics(); ViewGroup.LayoutParams createLayoutParams(int height, int width, int gravity, int bottomMargin); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java index 35d1b06..e0d73a7 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadDefaultConfig.java @@ -16,6 +16,7 @@ public ChatHeadDefaultConfig(Context context) { setHeadHorizontalSpacing(ChatHeadUtils.dpToPx(context, 10)); setHeadVerticalSpacing(ChatHeadUtils.dpToPx(context, 5)); setInitialPosition(new Point(0,ChatHeadUtils.dpToPx(context,0))); + setCloseButtonHidden(false); setCloseButtonWidth(ChatHeadUtils.dpToPx(context, 62)); setCloseButtonHeight(ChatHeadUtils.dpToPx(context, 62)); setCloseButtonBottomMargin(ChatHeadUtils.dpToPx(context, 50)); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java index 86e77b9..6e7ab6a 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java @@ -16,6 +16,7 @@ public abstract class FrameChatHeadContainer implements ChatHeadContainer { private final FrameLayout frameLayout; private final Context context; DisplayMetrics displayMetrics = new DisplayMetrics(); + private ChatHeadManager manager; public FrameChatHeadContainer(Context context) { this.context = context; @@ -24,6 +25,15 @@ public FrameChatHeadContainer(Context context) { addContainer(frameLayout, false); } + public ChatHeadManager getManager() { + return manager; + } + + @Override + public void onInitialized(ChatHeadManager manager) { + this.manager = manager; + } + public Context getContext() { return context; } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java index 7edea3f..afdc810 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java @@ -272,15 +272,15 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(metrics); this.displayMetrics = metrics; - setConfig(chatHeadDefaultConfig); + 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()); + chatHeadContainer.addView(arrowLayout, arrowLayout.getLayoutParams()); arrowLayout.setVisibility(View.GONE); springSystem = SpringSystem.create(); closeButton = new ChatHeadCloseButton(context, this, maxHeight, maxWidth); - ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getCloseButtonHeight(), getConfig().getCloseButtonWidth(), Gravity.TOP | Gravity.LEFT, 0); + ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(chatHeadDefaultConfig.getCloseButtonHeight(), chatHeadDefaultConfig.getCloseButtonWidth(), Gravity.TOP | Gravity.LEFT, 0); closeButton.setListener(this); chatHeadContainer.addView(closeButton, layoutParams); closeButtonShadow = new ImageView(getContext()); @@ -292,8 +292,10 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { arrangements.put(MaximizedArrangement.class, new MaximizedArrangement(this)); arrangements.put(CircularArrangement.class, new CircularArrangement(this)); setupOverlay(context); + setConfig(chatHeadDefaultConfig); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.DRAGGING, "dragging mode"); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.NOT_DRAGGING, "not dragging mode"); + chatHeadContainer.onInitialized(this); } private void setupOverlay(Context context) { @@ -433,7 +435,9 @@ public void bringToFront(ChatHead chatHead) { @Override public void onCloseButtonAppear() { - closeButtonShadow.setVisibility(View.VISIBLE); + if(!getConfig().isCloseButtonHidden()) { + closeButtonShadow.setVisibility(View.VISIBLE); + } } @Override @@ -487,6 +491,13 @@ public void setConfig(ChatHeadConfig config) { // 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, ChatHeadArrangement> arrangementEntry : arrangements.entrySet()) { arrangementEntry.getValue().onConfigChanged(config); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java index d39b242..2105112 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -1,8 +1,13 @@ package com.flipkart.chatheads.ui.container; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; import android.graphics.PixelFormat; import android.view.Gravity; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; @@ -37,8 +42,20 @@ public class WindowManagerContainer extends FrameChatHeadContainer { public WindowManagerContainer(Context context) { super(context); motionCaptureView = new View(context); - motionCaptureView.setOnTouchListener(new MotionCapturingTouchListener()); + MotionCapturingTouchListener listener = new MotionCapturingTouchListener(); + motionCaptureView.setOnTouchListener(listener); + motionCaptureView.setOnKeyListener(listener); addContainer(motionCaptureView, true); + registerReceiver(context); + } + + public void registerReceiver(Context context) { + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + minimize(); + } + }, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } public WindowManager getWindowManager() { @@ -165,12 +182,26 @@ public void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArr } } - protected class MotionCapturingTouchListener implements View.OnTouchListener { + protected class MotionCapturingTouchListener implements View.OnTouchListener, View.OnKeyListener { @Override public boolean onTouch(View v, MotionEvent event) { event.offsetLocation(getContainerX(v), getContainerY(v)); getFrameLayout().dispatchTouchEvent(event); return false; } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { + minimize(); + } + return true; + } + } + + private void minimize() { + if (!(getManager().getActiveArrangement() instanceof MinimizedArrangement)) { + getManager().setArrangement(MinimizedArrangement.class, null, true); + } } } From dd4aa395d34053ad2a34b821fa05ad739f1e94c8 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Wed, 9 Nov 2016 14:37:31 +0530 Subject: [PATCH 04/14] Added demo library dependency on circular-image library for generating chat heads drawable --- build.gradle | 1 + demo/build.gradle | 1 + .../springyheads/demo/ChatHeadService.java | 21 +++++++++++++- demo/src/main/res/values-w820dp/dimens.xml | 1 - demo/src/main/res/values/dimens.xml | 2 -- .../ui/container/WindowManagerContainer.java | 29 +++++++++++++++---- library/src/main/res/values-w820dp/dimens.xml | 1 - library/src/main/res/values/dimens.xml | 2 -- 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 53b51aa..1cd70b8 100755 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url "https://jitpack.io" } } } diff --git a/demo/build.gradle b/demo/build.gradle index 6adb63d..17e77b1 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -22,5 +22,6 @@ android { dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') // compile 'com.flipkart.springyheads:library:0.9.6' + compile 'com.github.flipkart-incubator:circular-image:1.0' compile project(':library') } diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java index 7a2204c..2f47b68 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java @@ -3,6 +3,7 @@ import android.app.Notification; import android.app.Service; import android.content.Intent; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.IBinder; import android.support.v4.app.NotificationCompat; @@ -17,6 +18,12 @@ import com.flipkart.chatheads.ui.MinimizedArrangement; import com.flipkart.chatheads.ui.container.DefaultChatHeadManager; import com.flipkart.chatheads.ui.container.WindowManagerContainer; +import com.flipkart.circularImageView.CircularDrawable; +import com.flipkart.circularImageView.OverlayArcDrawer; +import com.flipkart.circularImageView.TextDrawer; +import com.flipkart.circularImageView.notification.CircularNotificationDrawer; + +import java.util.Random; public class ChatHeadService extends Service { @@ -49,7 +56,7 @@ public View createView(String key, ChatHead chatHead, ViewGroup parent) { @Override public Drawable getChatHeadDrawable(String key) { - return getResources().getDrawable(R.drawable.head); + return ChatHeadService.this.getChatHeadDrawable(key); } @Override @@ -74,6 +81,18 @@ public View getTitleView(String key, ChatHead chatHead) { } + private Drawable getChatHeadDrawable(String key) { + Random rnd = new Random(); + int randomColor = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)); + CircularDrawable circularDrawable = new CircularDrawable(); + circularDrawable.setBitmapOrTextOrIcon(new TextDrawer().setText("C"+key).setBackgroundColor(randomColor)); + int badgeCount = (int) (Math.random() * 10f); + circularDrawable.setNotificationDrawer(new CircularNotificationDrawer().setNotificationText(String.valueOf(badgeCount)).setNotificationAngle(135).setNotificationColor(Color.WHITE, Color.RED)); + circularDrawable.setBorder(Color.WHITE, 3); + return circularDrawable; + + } + private void moveToForeground() { Notification notification = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.notification_template_icon_bg) diff --git a/demo/src/main/res/values-w820dp/dimens.xml b/demo/src/main/res/values-w820dp/dimens.xml index 63fc816..90bf001 100644 --- a/demo/src/main/res/values-w820dp/dimens.xml +++ b/demo/src/main/res/values-w820dp/dimens.xml @@ -2,5 +2,4 @@ - 64dp diff --git a/demo/src/main/res/values/dimens.xml b/demo/src/main/res/values/dimens.xml index 47c8224..5c1e384 100644 --- a/demo/src/main/res/values/dimens.xml +++ b/demo/src/main/res/values/dimens.xml @@ -1,5 +1,3 @@ - 16dp - 16dp diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java index 2105112..3a08608 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -20,6 +20,8 @@ import static android.content.Context.WINDOW_SERVICE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; /** @@ -126,9 +128,9 @@ protected int getContainerY(View container) { } protected WindowManager.LayoutParams createContainerLayoutParams(boolean focusable) { - int focusableFlag = FLAG_NOT_TOUCH_MODAL; + int focusableFlag = FLAG_NOT_TOUCH_MODAL | FLAG_ALT_FOCUSABLE_IM; if (!focusable) { - focusableFlag |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + focusableFlag |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE; } WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, WindowManager.LayoutParams.TYPE_PHONE, @@ -143,6 +145,10 @@ protected WindowManager.LayoutParams createContainerLayoutParams(boolean focusab @Override public void addContainer(View container, boolean focusable) { WindowManager.LayoutParams containerLayoutParams = createContainerLayoutParams(focusable); + addContainer(container, containerLayoutParams); + } + + public void addContainer(View container, WindowManager.LayoutParams containerLayoutParams) { container.setLayoutParams(containerLayoutParams); getWindowManager().addView(container, containerLayoutParams); } @@ -175,27 +181,40 @@ public void setViewY(View view, int yPosition) { public void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArrangement newArrangement) { currentArrangement = newArrangement; if (oldArrangement instanceof MinimizedArrangement && newArrangement instanceof MaximizedArrangement) { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); + layoutParams.flags |= FLAG_ALT_FOCUSABLE_IM; + windowManager.updateViewLayout(motionCaptureView,layoutParams); + setContainerX(motionCaptureView, 0); setContainerY(motionCaptureView, 0); setContainerWidth(motionCaptureView, getContainerWidth()); setContainerHeight(motionCaptureView, getContainerHeight()); + } else { + WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); + layoutParams.flags |= FLAG_NOT_FOCUSABLE; + windowManager.updateViewLayout(motionCaptureView,layoutParams); } } + private void removeContainer(View motionCaptureView) { + windowManager.removeView(motionCaptureView); + } + + protected class MotionCapturingTouchListener implements View.OnTouchListener, View.OnKeyListener { @Override public boolean onTouch(View v, MotionEvent event) { event.offsetLocation(getContainerX(v), getContainerY(v)); - getFrameLayout().dispatchTouchEvent(event); - return false; + return getFrameLayout().dispatchTouchEvent(event); } @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { minimize(); + return true; } - return true; + return false; } } diff --git a/library/src/main/res/values-w820dp/dimens.xml b/library/src/main/res/values-w820dp/dimens.xml index 63fc816..90bf001 100755 --- a/library/src/main/res/values-w820dp/dimens.xml +++ b/library/src/main/res/values-w820dp/dimens.xml @@ -2,5 +2,4 @@ - 64dp diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml index 47c8224..5c1e384 100755 --- a/library/src/main/res/values/dimens.xml +++ b/library/src/main/res/values/dimens.xml @@ -1,5 +1,3 @@ - 16dp - 16dp From d38b64d209ca375c1e4a9bad6a2842dfd3388c6a Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Thu, 10 Nov 2016 14:29:11 +0530 Subject: [PATCH 05/14] fixed focusability problem --- .../ui/container/WindowManagerContainer.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java index 3a08608..91f7cd1 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -22,7 +22,10 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.TYPE_PHONE; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; /** * Created by kiran.kumar on 08/11/16. @@ -43,7 +46,7 @@ public class WindowManagerContainer extends FrameChatHeadContainer { public WindowManagerContainer(Context context) { super(context); - motionCaptureView = new View(context); + motionCaptureView = new MotionCaptureView(context); MotionCapturingTouchListener listener = new MotionCapturingTouchListener(); motionCaptureView.setOnTouchListener(listener); motionCaptureView.setOnKeyListener(listener); @@ -70,7 +73,7 @@ public WindowManager getWindowManager() { @Override public int getContainerHeight() { if (cachedHeight <= 0) { - cachedHeight = windowManager.getDefaultDisplay().getHeight(); + cachedHeight = getWindowManager().getDefaultDisplay().getHeight(); } return cachedHeight; } @@ -78,7 +81,7 @@ public int getContainerHeight() { @Override public int getContainerWidth() { if (cachedWidth <= 0) { - cachedWidth = windowManager.getDefaultDisplay().getWidth(); + cachedWidth = getWindowManager().getDefaultDisplay().getWidth(); } return cachedWidth; } @@ -128,12 +131,14 @@ protected int getContainerY(View container) { } protected WindowManager.LayoutParams createContainerLayoutParams(boolean focusable) { - int focusableFlag = FLAG_NOT_TOUCH_MODAL | FLAG_ALT_FOCUSABLE_IM; - if (!focusable) { - focusableFlag |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE; + int focusableFlag; + if (focusable) { + focusableFlag = FLAG_NOT_TOUCH_MODAL; + } else { + focusableFlag = FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE; } WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, - WindowManager.LayoutParams.TYPE_PHONE, + TYPE_PHONE, focusableFlag, PixelFormat.TRANSLUCENT); layoutParams.x = 0; @@ -182,17 +187,16 @@ public void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArr currentArrangement = newArrangement; if (oldArrangement instanceof MinimizedArrangement && newArrangement instanceof MaximizedArrangement) { WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); - layoutParams.flags |= FLAG_ALT_FOCUSABLE_IM; - windowManager.updateViewLayout(motionCaptureView,layoutParams); + layoutParams.flags &= ~FLAG_NOT_FOCUSABLE; //remove focusability setContainerX(motionCaptureView, 0); setContainerY(motionCaptureView, 0); setContainerWidth(motionCaptureView, getContainerWidth()); setContainerHeight(motionCaptureView, getContainerHeight()); + } else { WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); - layoutParams.flags |= FLAG_NOT_FOCUSABLE; - windowManager.updateViewLayout(motionCaptureView,layoutParams); + layoutParams.flags |= FLAG_NOT_FOCUSABLE; //add focusability } } @@ -210,7 +214,7 @@ public boolean onTouch(View v, MotionEvent event) { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK && currentArrangement instanceof MaximizedArrangement) { minimize(); return true; } @@ -223,4 +227,11 @@ private void minimize() { getManager().setArrangement(MinimizedArrangement.class, null, true); } } + + private class MotionCaptureView extends View { + public MotionCaptureView(Context context) { + super(context); + } + + } } From dab59444c937c221c3a25c3d9504d2846bdecaeb Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Sun, 13 Nov 2016 22:30:41 +0530 Subject: [PATCH 06/14] fixed more focusability problems, rotation issues --- .../springyheads/demo/ChatHeadService.java | 38 ++++++---- demo/src/main/res/layout/fragment_test.xml | 2 +- .../chatheads/ui/ChatHeadCloseButton.java | 37 +++++++-- .../chatheads/ui/ChatHeadContainer.java | 9 +-- .../chatheads/ui/ChatHeadManager.java | 4 +- .../chatheads/ui/FrameChatHeadContainer.java | 21 +++-- .../chatheads/ui/HostFrameLayout.java | 49 ++++++++++++ .../chatheads/ui/MaximizedArrangement.java | 11 ++- .../chatheads/ui/MinimizedArrangement.java | 4 +- .../flipkart/chatheads/ui/UpArrowLayout.java | 5 ++ .../ui/container/DefaultChatHeadManager.java | 67 +++++++++++----- .../ui/container/WindowManagerContainer.java | 76 +++++++++---------- 12 files changed, 223 insertions(+), 100 deletions(-) create mode 100644 library/src/main/java/com/flipkart/chatheads/ui/HostFrameLayout.java diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java index 2f47b68..cece115 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java @@ -3,6 +3,7 @@ import android.app.Notification; import android.app.Service; import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.IBinder; @@ -13,22 +14,24 @@ import android.widget.TextView; import com.flipkart.chatheads.ui.ChatHead; -import com.flipkart.chatheads.ui.ChatHeadContainer; import com.flipkart.chatheads.ui.ChatHeadViewAdapter; import com.flipkart.chatheads.ui.MinimizedArrangement; import com.flipkart.chatheads.ui.container.DefaultChatHeadManager; import com.flipkart.chatheads.ui.container.WindowManagerContainer; import com.flipkart.circularImageView.CircularDrawable; -import com.flipkart.circularImageView.OverlayArcDrawer; import com.flipkart.circularImageView.TextDrawer; import com.flipkart.circularImageView.notification.CircularNotificationDrawer; +import java.util.HashMap; +import java.util.Map; import java.util.Random; public class ChatHeadService extends Service { - private DefaultChatHeadManager chatContainer; + private DefaultChatHeadManager chatHeadManager; private int chatHeadIdentifier = 0; + private WindowManagerContainer windowManagerContainer; + private Map viewCache = new HashMap<>(); @Override @@ -41,17 +44,22 @@ public IBinder onBind(Intent intent) { public void onCreate() { super.onCreate(); - ChatHeadContainer chatHeadContainer = new WindowManagerContainer(this); - chatContainer = new DefaultChatHeadManager(this, chatHeadContainer); - chatContainer.setViewAdapter(new ChatHeadViewAdapter() { + windowManagerContainer = new WindowManagerContainer(this); + chatHeadManager = new DefaultChatHeadManager(this, windowManagerContainer); + chatHeadManager.setViewAdapter(new ChatHeadViewAdapter() { @Override public View createView(String key, ChatHead chatHead, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); - View view = inflater.inflate(R.layout.fragment_test, parent, false); - TextView identifier = (TextView) view.findViewById(R.id.identifier); - identifier.setText(key); - return view; + View cachedView = viewCache.get(key); + if (cachedView == null) { + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.fragment_test, parent, false); + TextView identifier = (TextView) view.findViewById(R.id.identifier); + identifier.setText(key); + cachedView = view; + viewCache.put(key, view); + } + return cachedView; } @Override @@ -74,18 +82,18 @@ public View getTitleView(String key, ChatHead chatHead) { addChatHead(); addChatHead(); addChatHead(); - chatContainer.setArrangement(MinimizedArrangement.class, null); - chatContainer.onMeasure(); + chatHeadManager.setArrangement(MinimizedArrangement.class, null); moveToForeground(); } + private Drawable getChatHeadDrawable(String key) { Random rnd = new Random(); int randomColor = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)); CircularDrawable circularDrawable = new CircularDrawable(); - circularDrawable.setBitmapOrTextOrIcon(new TextDrawer().setText("C"+key).setBackgroundColor(randomColor)); + circularDrawable.setBitmapOrTextOrIcon(new TextDrawer().setText("C" + key).setBackgroundColor(randomColor)); int badgeCount = (int) (Math.random() * 10f); circularDrawable.setNotificationDrawer(new CircularNotificationDrawer().setNotificationText(String.valueOf(badgeCount)).setNotificationAngle(135).setNotificationColor(Color.WHITE, Color.RED)); circularDrawable.setBorder(Color.WHITE, 3); @@ -104,7 +112,7 @@ private void moveToForeground() { private void addChatHead() { chatHeadIdentifier++; - chatContainer.addChatHead(String.valueOf(chatHeadIdentifier), false, true); + chatHeadManager.addChatHead(String.valueOf(chatHeadIdentifier), false, true); } @Override diff --git a/demo/src/main/res/layout/fragment_test.xml b/demo/src/main/res/layout/fragment_test.xml index 3563d05..a642319 100755 --- a/demo/src/main/res/layout/fragment_test.xml +++ b/demo/src/main/res/layout/fragment_test.xml @@ -5,7 +5,7 @@ android:background="@drawable/rounded_white" android:orientation="vertical"> - { * Should be called when measuring of the container is done. * Typically called from onMeasure or onLayout * Only when {@link ChatHeadContainer#getContainerHeight()} && {@link ChatHeadContainer#getContainerWidth()} returns a positive value will arrangements start working + * @param height + * @param width */ - void onMeasure(); + void onMeasure(int height, int width); /** * Adds and returns the created chat head diff --git a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java index 6e7ab6a..9a6d58a 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/FrameChatHeadContainer.java @@ -13,18 +13,16 @@ public abstract class FrameChatHeadContainer implements ChatHeadContainer { - private final FrameLayout frameLayout; + private HostFrameLayout frameLayout; private final Context context; DisplayMetrics displayMetrics = new DisplayMetrics(); private ChatHeadManager manager; public FrameChatHeadContainer(Context context) { this.context = context; - FrameLayout frameLayout = new FrameLayout(context); - this.frameLayout = frameLayout; - addContainer(frameLayout, false); } + public ChatHeadManager getManager() { return manager; } @@ -32,13 +30,18 @@ public ChatHeadManager getManager() { @Override public void onInitialized(ChatHeadManager manager) { this.manager = manager; + HostFrameLayout frameLayout = new HostFrameLayout(context, this, manager); + frameLayout.setFocusable(true); + frameLayout.setFocusableInTouchMode(true); + this.frameLayout = frameLayout; + addContainer(frameLayout, false); } public Context getContext() { return context; } - public FrameLayout getFrameLayout() { + public HostFrameLayout getFrameLayout() { return frameLayout; } @@ -47,6 +50,11 @@ public void addView(View view, ViewGroup.LayoutParams layoutParams) { frameLayout.addView(view, layoutParams); } + @Override + public void requestLayout() { + frameLayout.requestLayout(); + } + @Override public void removeView(View view) { frameLayout.removeView(view); @@ -88,10 +96,9 @@ public int getViewY(View view) { } @Override - public void setZOrder(View view, int zIndex) { + public void bringToFront(View view) { view.bringToFront(); } public abstract void addContainer(View container, boolean focusable); - } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/HostFrameLayout.java b/library/src/main/java/com/flipkart/chatheads/ui/HostFrameLayout.java new file mode 100644 index 0000000..d6ac5c9 --- /dev/null +++ b/library/src/main/java/com/flipkart/chatheads/ui/HostFrameLayout.java @@ -0,0 +1,49 @@ +package com.flipkart.chatheads.ui; + +import android.content.Context; +import android.view.KeyEvent; +import android.widget.FrameLayout; + +/** + * Created by kiran.kumar on 11/11/16. + */ +public class HostFrameLayout extends FrameLayout { + private final ChatHeadManager manager; + private final ChatHeadContainer container; + + public HostFrameLayout(Context context, ChatHeadContainer chatHeadContainer, ChatHeadManager manager) { + super(context); + this.manager = manager; + this.container = chatHeadContainer; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + manager.onMeasure(getMeasuredHeight(), getMeasuredWidth()); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + manager.onSizeChanged(w,h,oldw,oldh); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + boolean handled = super.dispatchKeyEvent(event); + if(!handled) { + if (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + minimize(); + return true; + } + } + return handled; + } + + public void minimize() { + if (!(manager.getActiveArrangement() instanceof MinimizedArrangement)) { + manager.setArrangement(MinimizedArrangement.class, null, true); + } + } +} diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java index d5b03d5..16365e2 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.support.v4.util.ArrayMap; import android.view.View; +import android.view.ViewGroup; import com.facebook.rebound.Spring; import com.flipkart.chatheads.ChatHeadUtils; @@ -286,10 +287,18 @@ private void showView(ChatHead activeChatHead, double dx, double dy, double dist 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 activeChatHead) { UpArrowLayout arrowLayout = getArrowLayout(); + getArrowLayout().removeAllViews(); manager.addView(activeChatHead, arrowLayout); - arrowLayout.bringToFront(); + sendViewToBack(manager.getOverlayView()); Point point = positions.get(activeChatHead); if (point != null) { int padding = manager.getConfig().getHeadVerticalSpacing(maxWidth, maxHeight); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java index 7e66acd..9d83bc2 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java @@ -110,7 +110,7 @@ public void onSpringUpdate(Spring spring) { }); currentSpring = verticalSpringChain.getAllSprings().get(verticalSpringChain.getAllSprings().size() - 1); currentSpring.setCurrentValue(chatHead.getVerticalSpring().getCurrentValue()); - manager.getChatHeadContainer().setZOrder(chatHead,zIndex); + manager.getChatHeadContainer().bringToFront(chatHead); zIndex++; } } @@ -121,7 +121,7 @@ public void onSpringUpdate(Spring spring) { idleStateX = container.getConfig().getInitialPosition().x; } if (hero != null && hero.getHorizontalSpring()!=null && hero.getVerticalSpring()!=null ) { - manager.getChatHeadContainer().setZOrder(hero,zIndex); + manager.getChatHeadContainer().bringToFront(hero); horizontalSpringChain.addSpring(new SimpleSpringListener() { }); verticalSpringChain.addSpring(new SimpleSpringListener() { diff --git a/library/src/main/java/com/flipkart/chatheads/ui/UpArrowLayout.java b/library/src/main/java/com/flipkart/chatheads/ui/UpArrowLayout.java index 4e69ce6..073685f 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/UpArrowLayout.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/UpArrowLayout.java @@ -211,5 +211,10 @@ public LayoutParams generateLayoutParams(AttributeSet attrs) { return new FrameLayout.LayoutParams(getContext(), attrs); } + @Override + public void removeAllViews() { + super.removeAllViews(); + addView(arrowView); + } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java index afdc810..86d1420 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/DefaultChatHeadManager.java @@ -167,13 +167,33 @@ public void selectChatHead(T key) { @Override - public void onMeasure() { - maxHeight = chatHeadContainer.getContainerHeight(); - maxWidth = chatHeadContainer.getContainerWidth(); + 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; + 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 @@ -191,7 +211,7 @@ public ChatHead addChatHead(T key, boolean isSticky, boolean animated) { chatHead = new ChatHead(this, springSystem, getContext(), isSticky); chatHead.setKey(key); chatHeads.add(chatHead); - ViewGroup.LayoutParams layoutParams = chatHeadContainer.createLayoutParams(getConfig().getHeadWidth(), getConfig().getHeadHeight(), Gravity.LEFT | Gravity.TOP, 0); + 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) { @@ -268,6 +288,7 @@ public ChatHeadOverlayView getOverlayView() { } 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); @@ -280,7 +301,7 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { 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.LEFT, 0); + 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()); @@ -295,7 +316,6 @@ private void init(Context context, ChatHeadConfig chatHeadDefaultConfig) { setConfig(chatHeadDefaultConfig); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.DRAGGING, "dragging mode"); SpringConfigRegistry.getInstance().addSpringConfig(SpringConfigsHolder.NOT_DRAGGING, "not dragging mode"); - chatHeadContainer.onInitialized(this); } private void setupOverlay(Context context) { @@ -311,7 +331,7 @@ public double getDistanceCloseButtonFromHead(float touchX, float touchY) { } else { int left = closeButton.getLeft(); int top = closeButton.getTop(); - double xDiff = touchX - left - closeButton.getMeasuredWidth() / 2; + 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; @@ -342,31 +362,36 @@ public void setArrangement(final Class arrangemen @Override public void setArrangement(final Class arrangement, Bundle extras, boolean animated) { this.requestedArrangement = new ArrangementChangeRequest(arrangement, extras, animated); - onMeasure(); + chatHeadContainer.requestLayout(); } /** * Should only be called after onMeasure * - * @param requestedArrangement + * @param requestedArrangementParam */ - private void setArrangementImpl(ArrangementChangeRequest requestedArrangement) { - ChatHeadArrangement chatHeadArrangement = arrangements.get(requestedArrangement.getArrangement()); + private void setArrangementImpl(ArrangementChangeRequest requestedArrangementParam) { + boolean hasChanged = false; + ChatHeadArrangement requestedArrangement = arrangements.get(requestedArrangementParam.getArrangement()); ChatHeadArrangement oldArrangement = null; - ChatHeadArrangement newArrangement = chatHeadArrangement; - Bundle extras = requestedArrangement.getExtras(); + ChatHeadArrangement newArrangement = requestedArrangement; + Bundle extras = requestedArrangementParam.getExtras(); + if(activeArrangement!=requestedArrangement) hasChanged = true; if (extras == null) extras = new Bundle(); - if (activeArrangement != null && chatHeadArrangement != activeArrangement) { + if (activeArrangement != null) { extras.putAll(activeArrangement.getRetainBundle()); activeArrangement.onDeactivate(maxWidth, maxHeight); oldArrangement = activeArrangement; } - activeArrangement = chatHeadArrangement; + activeArrangement = requestedArrangement; activeArrangementBundle = extras; - chatHeadArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangement.isAnimated()); - chatHeadContainer.onArrangementChanged(oldArrangement, newArrangement); - if (listener != null) listener.onChatHeadArrangementChanged(oldArrangement, newArrangement); + requestedArrangement.onActivate(this, extras, maxWidth, maxHeight, requestedArrangementParam.isAnimated()); + if(hasChanged) { + chatHeadContainer.onArrangementChanged(oldArrangement, newArrangement); + if (listener != null) + listener.onChatHeadArrangementChanged(oldArrangement, newArrangement); + } } @@ -435,7 +460,7 @@ public void bringToFront(ChatHead chatHead) { @Override public void onCloseButtonAppear() { - if(!getConfig().isCloseButtonHidden()) { + if (!getConfig().isCloseButtonHidden()) { closeButtonShadow.setVisibility(View.VISIBLE); } } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java index 91f7cd1..bc75992 100644 --- a/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/container/WindowManagerContainer.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.view.Gravity; @@ -11,9 +12,11 @@ import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; +import android.widget.FrameLayout; import com.flipkart.chatheads.ui.ChatHead; import com.flipkart.chatheads.ui.ChatHeadArrangement; +import com.flipkart.chatheads.ui.ChatHeadManager; import com.flipkart.chatheads.ui.FrameChatHeadContainer; import com.flipkart.chatheads.ui.MaximizedArrangement; import com.flipkart.chatheads.ui.MinimizedArrangement; @@ -37,7 +40,7 @@ public class WindowManagerContainer extends FrameChatHeadContainer { * This view is required since window managers will delegate the touch events to the window beneath it only if they are outside the bounds. * {@link android.view.WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL} */ - private final View motionCaptureView; + private View motionCaptureView; private int cachedHeight; private int cachedWidth; @@ -46,19 +49,23 @@ public class WindowManagerContainer extends FrameChatHeadContainer { public WindowManagerContainer(Context context) { super(context); - motionCaptureView = new MotionCaptureView(context); + } + + @Override + public void onInitialized(ChatHeadManager manager) { + super.onInitialized(manager); + motionCaptureView = new MotionCaptureView(getContext()); MotionCapturingTouchListener listener = new MotionCapturingTouchListener(); motionCaptureView.setOnTouchListener(listener); - motionCaptureView.setOnKeyListener(listener); addContainer(motionCaptureView, true); - registerReceiver(context); + registerReceiver(getContext()); } public void registerReceiver(Context context) { context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - minimize(); + getFrameLayout().minimize(); } }, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } @@ -70,22 +77,6 @@ public WindowManager getWindowManager() { return windowManager; } - @Override - public int getContainerHeight() { - if (cachedHeight <= 0) { - cachedHeight = getWindowManager().getDefaultDisplay().getHeight(); - } - return cachedHeight; - } - - @Override - public int getContainerWidth() { - if (cachedWidth <= 0) { - cachedWidth = getWindowManager().getDefaultDisplay().getWidth(); - } - return cachedWidth; - } - protected void setContainerHeight(View container, int height) { WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(container); layoutParams.height = height; @@ -186,47 +177,54 @@ public void setViewY(View view, int yPosition) { public void onArrangementChanged(ChatHeadArrangement oldArrangement, ChatHeadArrangement newArrangement) { currentArrangement = newArrangement; if (oldArrangement instanceof MinimizedArrangement && newArrangement instanceof MaximizedArrangement) { + // about to be maximized WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); - layoutParams.flags &= ~FLAG_NOT_FOCUSABLE; //remove focusability + layoutParams.flags |= FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE; + windowManager.updateViewLayout(motionCaptureView,layoutParams); + + layoutParams = getOrCreateLayoutParamsForContainer(getFrameLayout()); + layoutParams.flags &= ~FLAG_NOT_FOCUSABLE; //add focusability + layoutParams.flags &= ~FLAG_NOT_TOUCHABLE; //add focusability + layoutParams.flags |= FLAG_NOT_TOUCH_MODAL; + + windowManager.updateViewLayout(getFrameLayout(),layoutParams); setContainerX(motionCaptureView, 0); setContainerY(motionCaptureView, 0); - setContainerWidth(motionCaptureView, getContainerWidth()); - setContainerHeight(motionCaptureView, getContainerHeight()); + setContainerWidth(motionCaptureView, getFrameLayout().getMeasuredWidth()); + setContainerHeight(motionCaptureView, getFrameLayout().getMeasuredHeight()); } else { + // about to be minimized WindowManager.LayoutParams layoutParams = getOrCreateLayoutParamsForContainer(motionCaptureView); - layoutParams.flags |= FLAG_NOT_FOCUSABLE; //add focusability + layoutParams.flags |= FLAG_NOT_FOCUSABLE; //remove focusability + layoutParams.flags &= ~FLAG_NOT_TOUCHABLE; //add touch + layoutParams.flags |= FLAG_NOT_TOUCH_MODAL; //add touch + windowManager.updateViewLayout(motionCaptureView,layoutParams); + + layoutParams = getOrCreateLayoutParamsForContainer(getFrameLayout()); + layoutParams.flags |= FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE; + windowManager.updateViewLayout(getFrameLayout(),layoutParams); } } + + private void removeContainer(View motionCaptureView) { windowManager.removeView(motionCaptureView); } - protected class MotionCapturingTouchListener implements View.OnTouchListener, View.OnKeyListener { + protected class MotionCapturingTouchListener implements View.OnTouchListener{ @Override public boolean onTouch(View v, MotionEvent event) { event.offsetLocation(getContainerX(v), getContainerY(v)); return getFrameLayout().dispatchTouchEvent(event); } - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK && currentArrangement instanceof MaximizedArrangement) { - minimize(); - return true; - } - return false; - } } - private void minimize() { - if (!(getManager().getActiveArrangement() instanceof MinimizedArrangement)) { - getManager().setArrangement(MinimizedArrangement.class, null, true); - } - } + private class MotionCaptureView extends View { public MotionCaptureView(Context context) { From 0ad61cc70bed7216482e78467b7c61df58ae3b6b Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 15 Nov 2016 01:07:41 +0530 Subject: [PATCH 07/14] fixing close button not disappearing problem --- .../chatheads/ui/ChatHeadCloseButton.java | 27 +++++++++++++------ .../chatheads/ui/MaximizedArrangement.java | 27 ++++++++++++++++++- .../chatheads/ui/MinimizedArrangement.java | 26 +++++++++++++++--- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java index 43482ff..31faa29 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadCloseButton.java @@ -55,8 +55,6 @@ public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); int x = getXFromSpring(spring); manager.getChatHeadContainer().setViewX(ChatHeadCloseButton.this, x); -// System.out.println("spring x = [" + x + "] center "+centerX); - } }); ySpring = springSystem.createSpring(); @@ -66,7 +64,6 @@ public void onSpringUpdate(Spring spring) { super.onSpringUpdate(spring); int y = getYFromSpring(spring); manager.getChatHeadContainer().setViewY(ChatHeadCloseButton.this, y); -// System.out.println("spring y = [" + y + "] center "+centerY); } }); scaleSpring = springSystem.createSpring(); @@ -114,9 +111,17 @@ public void onRelease() { } public void disappear(boolean immediate, boolean animate) { - ySpring.setEndValue(mParentHeight); + ySpring.setEndValue(mParentHeight - centerY + chatHeadManager.getConfig().getCloseButtonHeight()); ySpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); xSpring.setEndValue(0); + ySpring.addListener(new SimpleSpringListener() { + @Override + public void onSpringAtRest(Spring spring) { + super.onSpringAtRest(spring); + + ySpring.removeListener(this); + } + }); scaleSpring.setEndValue(0.1f); if (!animate) { ySpring.setCurrentValue(mParentHeight, true); @@ -139,10 +144,16 @@ public void onParentHeightRefreshed() { } public void setCenter(int x, int y) { - this.centerX = x; - this.centerY = y; - xSpring.setCurrentValue(0); - ySpring.setCurrentValue(0); + boolean changed = false; + if (x != centerX || y != centerY) { + changed = true; + } + if(changed) { + this.centerX = x; + this.centerY = y; + xSpring.setCurrentValue(0); + ySpring.setCurrentValue(0); + } } public void pointTo(float x, float y) { diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java index 16365e2..da4c333 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MaximizedArrangement.java @@ -8,6 +8,7 @@ import android.view.View; import android.view.ViewGroup; +import com.facebook.rebound.SimpleSpringListener; import com.facebook.rebound.Spring; import com.flipkart.chatheads.ChatHeadUtils; @@ -29,6 +30,7 @@ public class MaximizedArrangement extends ChatHeadArrang private int maxDistanceFromOriginal; private int topPadding; private boolean isActive = false; + private boolean isTransitioning = false; public MaximizedArrangement(ChatHeadManager manager) { @@ -43,6 +45,7 @@ public void setContainer(ChatHeadManager 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; @@ -96,6 +99,28 @@ public void onClick(View v) { }); 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); + } + }); } } @@ -235,7 +260,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW manager.getCloseButton().disappear(false, true); manager.captureChatHeads(activeChatHead); } - if (!activeVerticalSpring.isAtRest()) { + if (!activeVerticalSpring.isAtRest() && !isTransitioning) { manager.getCloseButton().appear(); } else { manager.getCloseButton().disappear(true, true); diff --git a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java index 9d83bc2..00dae06 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/MinimizedArrangement.java @@ -36,6 +36,14 @@ public void onSpringUpdate(Spring spring) { 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 @@ -43,7 +51,16 @@ 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; @@ -69,6 +86,7 @@ public void setContainer(ChatHeadManager 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); } @@ -120,7 +138,7 @@ public void onSpringUpdate(Spring spring) { if (idleStateX == Integer.MIN_VALUE) { idleStateX = container.getConfig().getInitialPosition().x; } - if (hero != null && hero.getHorizontalSpring()!=null && hero.getVerticalSpring()!=null ) { + if (hero != null && hero.getHorizontalSpring() != null && hero.getVerticalSpring() != null) { manager.getChatHeadContainer().bringToFront(hero); horizontalSpringChain.addSpring(new SimpleSpringListener() { }); @@ -161,7 +179,7 @@ public void onSpringUpdate(Spring spring) { @Override public void onChatHeadAdded(ChatHead chatHead, boolean animated) { - if (hero != null && hero.getHorizontalSpring()!=null && hero.getVerticalSpring()!=null) { + if (hero != null && hero.getHorizontalSpring() != null && hero.getVerticalSpring() != null) { chatHead.getHorizontalSpring().setCurrentValue(hero.getHorizontalSpring().getCurrentValue() - currentDelta); chatHead.getVerticalSpring().setCurrentValue(hero.getVerticalSpring().getCurrentValue()); } @@ -392,7 +410,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW activeChatHead.setState(ChatHead.State.CAPTURED); } - if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeHorizontalSpring.getSpringConfig()!= SpringConfigsHolder.CAPTURING) { + if (activeChatHead.getState() == ChatHead.State.CAPTURED && activeHorizontalSpring.getSpringConfig() != SpringConfigsHolder.CAPTURING) { activeHorizontalSpring.setAtRest(); activeVerticalSpring.setAtRest(); activeHorizontalSpring.setSpringConfig(SpringConfigsHolder.CAPTURING); @@ -405,7 +423,7 @@ public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxW manager.getCloseButton().disappear(false, true); manager.captureChatHeads(activeChatHead); } - if (!activeVerticalSpring.isAtRest()) { + if (!activeVerticalSpring.isAtRest() && !isTransitioning) { manager.getCloseButton().appear(); } else { manager.getCloseButton().disappear(true, true); From d05e92afb3915a8070eaca86dfb058e3f6027a94 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 15 Nov 2016 01:12:17 +0530 Subject: [PATCH 08/14] remove unused code --- .../chatheads/ui/ChatHeadViewAdapter.java | 6 - .../chatheads/ui/CircularArrangement.java | 440 ------------------ 2 files changed, 446 deletions(-) delete mode 100644 library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java diff --git a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java index 13765b6..842cc42 100755 --- a/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java +++ b/library/src/main/java/com/flipkart/chatheads/ui/ChatHeadViewAdapter.java @@ -25,11 +25,5 @@ public interface ChatHeadViewAdapter { */ public Drawable getChatHeadDrawable(T key); - /** - * Used for circular arrangement where a view is drawn under the touch point - * @return - */ - public Drawable getPointerDrawable(); - public View getTitleView(T key, ChatHead chatHead); } diff --git a/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java b/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java deleted file mode 100644 index 1753fcd..0000000 --- a/library/src/main/java/com/flipkart/chatheads/ui/CircularArrangement.java +++ /dev/null @@ -1,440 +0,0 @@ -package com.flipkart.chatheads.ui; - -import android.annotation.TargetApi; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.util.Pair; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.facebook.rebound.SimpleSpringListener; -import com.facebook.rebound.Spring; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by kiran.kumar on 07/04/15. - */ -public class CircularArrangement extends ChatHeadArrangement { - public static final String BUNDLE_KEY_X = "X"; - public static final String BUNDLE_KEY_Y = "Y"; - private final ImageView pointerViewMovable; - private final Spring pointerXSpring; - private final Spring pointerYSpring; - private int CLOSE_ATTRACTION_THRESHOLD; - private final Spring pointerScaleSpring; - private final ImageView pointerViewStatic; - private int RADIUS; - private boolean isActive = false; - private ChatHeadManager manager; - private ChatHead currentChatHead; - private int maxWidth; - private int maxHeight; - private RollState rollOverState; //whether we are over or out of a chat head - private ChatHead rollOverChatHead; //the chat head where we rolled over //will be non null if we are in roll over state. null if roll out state - private Bundle retainBundle; - - - public CircularArrangement(ChatHeadManager manager) { - this.manager = manager; - this.pointerViewMovable = new ImageView(manager.getContext()); - this.pointerViewStatic = new ImageView(manager.getContext()); - - //manager.getViewManager().addView(pointerViewMovable,pointerViewMovable.getLayoutParams()); - //manager.getViewManager().addView(pointerViewStatic,pointerViewStatic.getLayoutParams()); - this.pointerXSpring = manager.getSpringSystem().createSpring(); - this.pointerYSpring = manager.getSpringSystem().createSpring(); - this.pointerScaleSpring = manager.getSpringSystem().createSpring(); - this.pointerXSpring.addListener(new SimpleSpringListener() { - @Override - public void onSpringUpdate(Spring spring) { - super.onSpringUpdate(spring); - pointerViewMovable.setTranslationX((float) (spring.getCurrentValue() - pointerViewMovable.getMeasuredWidth() / 2)); - } - }); - this.pointerYSpring.addListener(new SimpleSpringListener() { - @Override - public void onSpringUpdate(Spring spring) { - super.onSpringUpdate(spring); - pointerViewMovable.setTranslationY((float) (spring.getCurrentValue() - pointerViewMovable.getMeasuredHeight() / 2)); - } - }); - this.pointerScaleSpring.addListener(new SimpleSpringListener() { - @Override - public void onSpringUpdate(Spring spring) { - super.onSpringUpdate(spring); - pointerViewMovable.setScaleX((float) spring.getCurrentValue()); - pointerViewMovable.setScaleY((float) spring.getCurrentValue()); - } - }); - - onConfigChanged(manager.getConfig()); - - } - - @Override - public void onConfigChanged(ChatHeadConfig newConfig) { - this.pointerViewMovable.setLayoutParams(new FrameLayout.LayoutParams(newConfig.getCircularRingWidth(), newConfig.getCircularRingHeight())); - this.pointerViewStatic.setLayoutParams(new FrameLayout.LayoutParams(newConfig.getCircularRingWidth(), newConfig.getCircularRingHeight())); - - } - - @Override - public Bundle getRetainBundle() { - return retainBundle; - } - - @Override - public boolean canDrag(ChatHead chatHead) { - return true; - } - - @Override - public void removeOldestChatHead() { - for (ChatHead chatHead : manager.getChatHeads()) { - if (!chatHead.isSticky()) { - manager.removeChatHead(chatHead.getKey(), false); - break; - } - } - } - - @Override - public void setContainer(ChatHeadManager container) { - this.manager = container; - } - - @Override - public void onActivate(ChatHeadManager container, Bundle extras, int maxWidth, int maxHeight, boolean animated) { - List chatHeads = container.getChatHeads(); - RADIUS = container.getConfig().getCircularFanOutRadius(maxWidth, maxHeight); - int headHeight = container.getConfig().getHeadHeight(); - int headWidth = container.getConfig().getHeadWidth(); - CLOSE_ATTRACTION_THRESHOLD = (int) (RADIUS * 0.5); - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - if(container.getChatHeads().size()>0) { - - Point pointTo = new Point(extras.getInt(BUNDLE_KEY_X), extras.getInt(BUNDLE_KEY_Y)); - int radius = RADIUS; - Pair angles = calculateStartEndAngles(pointTo, (float) (radius), 0, 0, maxWidth, maxHeight); - double totalSweepArea = (angles.second - angles.first); - if (chatHeads.size() > 0) { - double perHeadSweep = totalSweepArea / chatHeads.size(); - if (perHeadSweep > Math.PI / 5) { - perHeadSweep = Math.PI / 5; - totalSweepArea = perHeadSweep * chatHeads.size(); - } - } - int i = 0; - for (ChatHead chatHead : chatHeads) { - /** Horizontal **/ - chatHead.getHorizontalSpring().setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - double angle = angles.first + (angles.second - angles.first) / 2 - (totalSweepArea / 2); - if (chatHeads.size() > 1) { - angle += (float) i / ((float) chatHeads.size() - 1) * totalSweepArea; - } - double xValue = pointTo.x + radius * Math.cos(angle); - xValue -= container.getConfig().getHeadWidth() / 2; - chatHead.getHorizontalSpring().setEndValue(xValue); - - /** Vertical **/ - chatHead.getVerticalSpring().setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - angle = angles.first + (angles.second - angles.first) / 2 - (totalSweepArea / 2); - if (chatHeads.size() > 1) { - angle += (float) i / ((float) chatHeads.size() - 1) * totalSweepArea; - } - double yValue = pointTo.y + radius * Math.sin(angle); - yValue -= container.getConfig().getHeadHeight() / 2; - chatHead.getVerticalSpring().setEndValue(yValue); - i++; - } - - isActive = true; - this.manager = container; - container.showOverlayView(true); - container.getOverlayView().setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - deactivate(); - } - }); - Drawable pointerDrawable = container.getViewAdapter().getPointerDrawable(); - pointerViewMovable.setImageDrawable(pointerDrawable); - Drawable pointerDrawable2 = container.getViewAdapter().getPointerDrawable(); - pointerViewStatic.setImageDrawable(pointerDrawable2); - pointerViewMovable.setVisibility(View.VISIBLE); - pointerViewStatic.setVisibility(View.VISIBLE); - - pointerXSpring.setCurrentValue(pointTo.x); - pointerYSpring.setCurrentValue(pointTo.y); - pointerScaleSpring.setCurrentValue(0.5f); - pointerViewStatic.setTranslationX(pointTo.x - pointerViewStatic.getMeasuredWidth() / 2); - pointerViewStatic.setTranslationY(pointTo.y - pointerViewStatic.getMeasuredHeight() / 2); - pointerViewStatic.setScaleX(0.5f); - pointerViewStatic.setScaleY(0.5f); - currentChatHead = null; - } - retainBundle = extras; - - - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @Override - public boolean handleRawTouchEvent(MotionEvent event) { - super.handleRawTouchEvent(event); - if (event.getAction() == MotionEvent.ACTION_MOVE) { - boolean foundSpring = false; - List> chatHeads = manager.getChatHeads(); - for (ChatHead chatHead : chatHeads) { - double distance = Math.hypot(event.getX() - pointerViewMovable.getMeasuredWidth() / 2 - chatHead.getTranslationX(), event.getY() - pointerViewMovable.getMeasuredHeight() / 2 - chatHead.getTranslationY()); - if (distance < CLOSE_ATTRACTION_THRESHOLD) { - foundSpring = true; - pointerXSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - pointerXSpring.setEndValue(chatHead.getTranslationX() + chatHead.getMeasuredWidth() / 2); - pointerYSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - pointerYSpring.setEndValue(chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2); - pointerScaleSpring.setEndValue(1f); - if (currentChatHead != chatHead) { - manager.getOverlayView().drawPath(pointerViewStatic.getTranslationX() + pointerViewStatic.getMeasuredWidth() / 2, pointerViewStatic.getTranslationY() + pointerViewStatic.getMeasuredHeight() / 2, chatHead.getTranslationX() + chatHead.getMeasuredHeight() / 2, chatHead.getTranslationY() + chatHead.getMeasuredHeight() / 2); - } - currentChatHead = chatHead; - if (rollOverChatHead != chatHead && isActive) { - manager.onItemRollOver(chatHead); - rollOverChatHead = chatHead; - } - rollOverState = RollState.OVER; - break; - } - } - - if (!foundSpring) { - pointerXSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - pointerYSpring.setSpringConfig(SpringConfigsHolder.NOT_DRAGGING); - pointerXSpring.setEndValue(event.getX()); - pointerYSpring.setEndValue(event.getY()); - pointerScaleSpring.setEndValue(0.5f); - manager.getOverlayView().clearPath(); - onRollOut(); - currentChatHead = null; - } - } else if (event.getAction() == MotionEvent.ACTION_UP) { - manager.getOverlayView().clearPath(); - onRollOut(); - if (currentChatHead != null) { - boolean handled = manager.onItemSelected(currentChatHead); - if (!handled) { - deactivate(); - } - } else { - deactivate(); - } - - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - onRollOut(); - } - return true; - } - - private void onRollOut() { - if (rollOverState != RollState.OUT && rollOverChatHead != null) { - manager.onItemRollOut(rollOverChatHead); - rollOverChatHead = null; - } - rollOverState = RollState.OUT; - } - - /** - * Brute force method to find the start angles. Not very well thought out. - * This method will give the start and end angles where the arc cut out by the rectangle lies within the rectangle. - * - * @param pointTo - * @param radius - * @param left - * @param top - * @param right - * @param bottom - * @return - */ - private Pair calculateStartEndAngles(Point pointTo, float radius, int left, int top, int right, int bottom) { - Rect circleRect = new Rect((pointTo.x - (int) radius), (pointTo.y - (int) radius), (pointTo.x + (int) radius), (pointTo.y + (int) radius)); - Rect screenRect = new Rect(left, top, right, bottom); - List quadrants = findContainingQuadrants(circleRect, screenRect); - float minAngle = Float.MAX_VALUE, maxAngle = Float.MIN_VALUE; - if (quadrants.contains(Quadrant.IV) && quadrants.contains(Quadrant.I) && quadrants.size() == 2) { - //TODO this is a hack until a better way if figured out - minAngle = (float) (-Math.PI / 2); - maxAngle = (float) (Math.PI / 2); - } else { - if (quadrants.size() < 4) { - for (Quadrant quadrant : quadrants) { - double angle2 = (quadrant.getStartAngle()); - minAngle = (float) Math.min(minAngle, angle2); - double angle4 = (quadrant.getEndAngle()); - maxAngle = (float) Math.max(maxAngle, angle4); - } - } else { - minAngle = (float) Math.PI; - maxAngle = (float) (Math.PI * 2); - } - } - return new Pair<>(minAngle, maxAngle); - } - - private double convert(double angle) { - return ((angle + Math.PI) % (Math.PI * 2f)) - Math.PI; - } - - /** - * Returns the quadrants which the second rect contains in itself fully. - */ - private List findContainingQuadrants(Rect firstRect, Rect secondRect) { - List quadrants = new ArrayList<>(); - int radiusX = (firstRect.right - firstRect.left) / 2; - int radiusY = (firstRect.bottom - firstRect.top) / 2; - for (Quadrant quadrant : Quadrant.values()) { - int x = firstRect.centerX() + quadrant.getxSign() * radiusX; - int y = firstRect.centerY() + quadrant.getySign() * radiusY; - Rect quadrantRect = new Rect(Math.min(x, firstRect.centerX()), Math.min(y, firstRect.centerY()), Math.max(firstRect.centerX(), x), Math.max(firstRect.centerY(), y)); - if (secondRect.contains(quadrantRect)) { - quadrants.add(quadrant); - } - } - return quadrants; - } - - private void deactivate() { - manager.setArrangement(MinimizedArrangement.class, null); - } - - @Override - public Integer getHeroIndex() { - int heroIndex = 0; - List> chatHeads = manager.getChatHeads(); - int i = 0; - for (ChatHead chatHead : chatHeads) { - if (currentChatHead == chatHead) { - heroIndex = i; - } - i++; - } - return heroIndex; - } - - - @Override - public void onDeactivate(int maxWidth, int maxHeight) { - onRollOut(); - isActive = false; - pointerViewMovable.setVisibility(View.GONE); - pointerViewStatic.setVisibility(View.GONE); - this.manager.hideOverlayView(true); - currentChatHead = null; - } - - @Override - public void onSpringUpdate(ChatHead activeChatHead, boolean isDragging, int maxWidth, int maxHeight, Spring spring, Spring activeHorizontalSpring, Spring activeVerticalSpring, int totalVelocity) { - - } - - @Override - public boolean handleTouchUp(ChatHead activeChatHead, int xVelocity, int yVelocity, Spring activeHorizontalSpring, Spring activeVerticalSpring, boolean wasDragging) { - if (isActive) { - boolean handled = manager.onItemSelected(activeChatHead); - if (!handled) { - deactivate(); - } - return false; - - } else { - return true; - } - } - - @Override - public void onChatHeadAdded(ChatHead chatHead, boolean animated) { - onActivate(manager, retainBundle, maxWidth, maxHeight, true); - } - - @Override - public void onChatHeadRemoved(ChatHead removed) { - onActivate(manager, retainBundle, maxWidth, maxHeight, true); - } - - @Override - public void onCapture(ChatHeadManager container, ChatHead activeChatHead) { - - } - - @Override - public void selectChatHead(ChatHead chatHead) { - - } - - @Override - public void bringToFront(ChatHead chatHead) { - //everything is in front. nothing to do here. - } - - @Override - public void onReloadFragment(ChatHead chatHead) { - //nothing to do - } - - @Override - public boolean shouldShowCloseButton(ChatHead chatHead) { - return false; - } - - private enum RollState { - OVER, // over means that the finger is over the chat head - OUT; // out means that finger is outside the touch tolerance of chat head - } - - -// II | I -// | -// <------+------>x -// | -// III | IV - - private enum Quadrant { - IV(+1, +1, 0, Math.PI / 2), III(-1, +1, Math.PI / 2, Math.PI), II(-1, -1, Math.PI, 1.5 * Math.PI), I(+1, -1, 1.5 * Math.PI, 2 * Math.PI); - - public double getStartAngle() { - return startAngle; - } - - public double getEndAngle() { - return endAngle; - } - - public int getySign() { - return ySign; - } - - public int getxSign() { - return xSign; - } - - private final double startAngle; - private final double endAngle; - private final int ySign; - private final int xSign; - - Quadrant(int xSign, int ySign, double startAngle, double endAngle) { - this.startAngle = startAngle; - this.endAngle = endAngle; - this.xSign = xSign; - this.ySign = ySign; - } - } -} From b6c18a4b5976584f8a56f1e3d10e0be6e3ccae36 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 15 Nov 2016 01:39:04 +0530 Subject: [PATCH 09/14] added configuration page in demo app --- .../springyheads/demo/ChatHeadService.java | 58 ++++++++++---- .../springyheads/demo/FloatingActivity.java | 76 ++++++++++++++++++- demo/src/main/res/layout/activity_main.xml | 42 ++++++---- .../chatheads/ui/ChatHeadCloseButton.java | 2 - .../ui/container/DefaultChatHeadManager.java | 2 - .../ui/container/WindowManagerContainer.java | 17 +++-- 6 files changed, 151 insertions(+), 46 deletions(-) diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java index cece115..fc08cc8 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/ChatHeadService.java @@ -1,11 +1,13 @@ package com.flipkart.springyheads.demo; import android.app.Notification; +import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Binder; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.view.LayoutInflater; @@ -15,6 +17,7 @@ import com.flipkart.chatheads.ui.ChatHead; import com.flipkart.chatheads.ui.ChatHeadViewAdapter; +import com.flipkart.chatheads.ui.MaximizedArrangement; import com.flipkart.chatheads.ui.MinimizedArrangement; import com.flipkart.chatheads.ui.container.DefaultChatHeadManager; import com.flipkart.chatheads.ui.container.WindowManagerContainer; @@ -32,12 +35,24 @@ public class ChatHeadService extends Service { private int chatHeadIdentifier = 0; private WindowManagerContainer windowManagerContainer; private Map viewCache = new HashMap<>(); - + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + ChatHeadService getService() { + // Return this instance of LocalService so clients can call public methods + return ChatHeadService.this; + } + } @Override public IBinder onBind(Intent intent) { - // Not used - return null; + return mBinder; } @Override @@ -66,16 +81,6 @@ public View createView(String key, ChatHead chatHead, ViewGroup parent) { public Drawable getChatHeadDrawable(String key) { return ChatHeadService.this.getChatHeadDrawable(key); } - - @Override - public Drawable getPointerDrawable() { - return getResources().getDrawable(R.drawable.circular_ring); - } - - @Override - public View getTitleView(String key, ChatHead chatHead) { - return null; - } }); addChatHead(); @@ -83,7 +88,6 @@ public View getTitleView(String key, ChatHead chatHead) { addChatHead(); addChatHead(); chatHeadManager.setArrangement(MinimizedArrangement.class, null); - moveToForeground(); } @@ -104,19 +108,41 @@ private Drawable getChatHeadDrawable(String key) { private void moveToForeground() { Notification notification = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.notification_template_icon_bg) - .setContentText("Chat heads is active") + .setContentTitle("Springy heads") + .setContentText("Click to configure.") + .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, FloatingActivity.class), 0)) .build(); startForeground(1, notification); } - private void addChatHead() { + public void addChatHead() { chatHeadIdentifier++; chatHeadManager.addChatHead(String.valueOf(chatHeadIdentifier), false, true); } + public void removeChatHead() { + chatHeadIdentifier--; + chatHeadManager.removeChatHead(String.valueOf(chatHeadIdentifier), true); + } + + + public void removeAllChatHeads() { + chatHeadIdentifier = 0; + chatHeadManager.removeAllChatHeads(true); + } + + public void toggleArrangement() { + if (chatHeadManager.getActiveArrangement() instanceof MinimizedArrangement) { + chatHeadManager.setArrangement(MaximizedArrangement.class, null); + } else { + chatHeadManager.setArrangement(MinimizedArrangement.class, null); + } + } + @Override public void onDestroy() { super.onDestroy(); + windowManagerContainer.destroy(); } } \ No newline at end of file diff --git a/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java b/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java index cef174f..d199ca6 100644 --- a/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java +++ b/demo/src/main/java/com/flipkart/springyheads/demo/FloatingActivity.java @@ -1,18 +1,88 @@ package com.flipkart.springyheads.demo; import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.os.Bundle; +import android.os.IBinder; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; /** * Created by kiran.kumar on 06/02/16. */ -public class FloatingActivity extends Activity { +public class FloatingActivity extends Activity implements View.OnClickListener { + + private Button addButton; + private Button removeButton; + private Button removeAllButtons; + private Button toggleButton; + + private ChatHeadService chatHeadService; + private boolean bound; + /** + * Defines callbacks for service binding, passed to bindService() + */ + private ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + ChatHeadService.LocalBinder binder = (ChatHeadService.LocalBinder) service; + chatHeadService = binder.getService(); + bound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + bound = false; + } + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - startService(new Intent(this, ChatHeadService.class)); - finish(); + Intent intent = new Intent(this, ChatHeadService.class); + startService(intent); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + setupButtons(); + } + + private void setupButtons() { + setContentView(R.layout.activity_main); + + addButton = (Button) findViewById(R.id.add_head); + removeButton = (Button) findViewById(R.id.remove_head); + removeAllButtons = (Button) findViewById(R.id.remove_all_heads); + toggleButton = (Button) findViewById(R.id.toggle_arrangement); + + addButton.setOnClickListener(this); + removeButton.setOnClickListener(this); + removeAllButtons.setOnClickListener(this); + toggleButton.setOnClickListener(this); + } + + + @Override + public void onClick(View v) { + if (bound) { + if (v == addButton) { + chatHeadService.addChatHead(); + + } else if (v == removeButton) { + chatHeadService.removeChatHead(); + + } else if (v == removeAllButtons) { + chatHeadService.removeAllChatHeads(); + } else if (v == toggleButton) { + chatHeadService.toggleArrangement(); + } + } else { + Toast.makeText(this, "Service not bound", Toast.LENGTH_SHORT).show(); + } } } diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index 6c359be..1c64303 100755 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -1,29 +1,39 @@ + android:gravity="center_horizontal"> + android:gravity="center" + android:orientation="vertical"> - +