package com.reactnativenavigation.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.params.FabActionParams;
import com.reactnativenavigation.params.FabParams;
import com.reactnativenavigation.utils.ViewUtils;
import java.util.ArrayList;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
class FloatingActionButtonCoordinator {
private static final String TAG = "FloatingActionButtonCoo";
private static final int INITIAL_EXPENDED_FAB_ROTATION = -90;
private CoordinatorLayout parent;
private FabParams params;
private FloatingActionButton collapsedFab;
private FloatingActionButton expendedFab;
private final int crossFadeAnimationDuration;
private final int actionSize;
private final int margin = (int) ViewUtils.convertDpToPixel(16);
private FloatingActionButtonAnimator fabAnimator;
private final ArrayList<FloatingActionButton> actions;
FloatingActionButtonCoordinator(CoordinatorLayout parent) {
this.parent = parent;
actions = new ArrayList<>();
crossFadeAnimationDuration = parent.getResources().getInteger(android.R.integer.config_shortAnimTime);
actionSize = (int) ViewUtils.convertDpToPixel(40);
}
public void add(final FabParams params) {
Log.i(TAG, "add() called with: params = [" + params + "]");
if (hasFab()) {
remove(new Runnable() {
@Override
public void run() {
add(params);
}
});
return;
}
this.params = params;
if (!params.isValid()) {
return;
}
createCollapsedFab();
createExpendedFab();
setStyle();
fabAnimator = new FloatingActionButtonAnimator(collapsedFab, expendedFab, crossFadeAnimationDuration);
fabAnimator.show();
}
void remove(@Nullable final Runnable onComplete) {
if (!hasFab()) {
if (onComplete != null) {
onComplete.run();
}
return;
}
if (fabAnimator != null) {
fabAnimator.removeFabFromScreen(expendedFab, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
removeAllViews();
if (onComplete != null) {
onComplete.run();
}
}
});
fabAnimator.removeFabFromScreen(collapsedFab, null);
fabAnimator.removeActionsFromScreen(actions);
}
}
private boolean hasFab() {
return collapsedFab != null || expendedFab != null;
}
private void removeAllViews() {
parent.removeView(collapsedFab);
parent.removeView(expendedFab);
collapsedFab = null;
expendedFab = null;
for (FloatingActionButton action : actions) {
((CoordinatorLayout.LayoutParams) action.getLayoutParams()).setBehavior(null);
parent.removeView(action);
}
actions.clear();
}
private void createCollapsedFab() {
collapsedFab = createFab(params.collapsedIcon);
parent.addView(collapsedFab, createFabLayoutParams());
collapsedFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (params.hasExpendedState()) {
fabAnimator.hideCollapsed();
fabAnimator.showExpended();
showActions();
}
NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(params.collapsedId, params.navigatorEventId);
}
});
}
private void createExpendedFab() {
expendedFab = createFab(params.expendedIcon);
parent.addView(expendedFab, createFabLayoutParams());
expendedFab.setVisibility(View.GONE);
expendedFab.setRotation(INITIAL_EXPENDED_FAB_ROTATION);
expendedFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fabAnimator.collapse();
NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(params.expendedId, params.navigatorEventId);
}
});
}
private FloatingActionButton createFab(Drawable icon) {
FloatingActionButton fab = new FloatingActionButton(parent.getContext());
fab.setId(ViewUtils.generateViewId());
fab.setImageDrawable(icon);
return fab;
}
private CoordinatorLayout.LayoutParams createFabLayoutParams() {
final CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
lp.gravity = Gravity.RIGHT | Gravity.BOTTOM;
lp.bottomMargin = margin;
lp.rightMargin = margin;
lp.topMargin = margin;
return lp;
}
private void setStyle() {
collapsedFab.setBackgroundTintList(ColorStateList.valueOf(params.backgroundColor.getColor()));
expendedFab.setBackgroundTintList(ColorStateList.valueOf(params.backgroundColor.getColor()));
}
private void showActions() {
if (actions.size() > 0) {
return;
}
for (int i = 0; i < params.actions.size(); i++) {
FloatingActionButton action = createAction(i);
actions.add(action);
parent.addView(action);
}
}
private FloatingActionButton createAction(int index) {
final FabActionParams actionParams = params.actions.get(index);
FloatingActionButton action = createFab(actionParams.icon);
action.setLayoutParams(createActionLayoutParams(index));
action.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavigationApplication.instance.getEventEmitter().sendNavigatorEvent(actionParams.id, actionParams.navigatorEventId);
fabAnimator.collapse();
}
});
if (actionParams.backgroundColor.hasColor()) {
action.setBackgroundTintList(ColorStateList.valueOf(actionParams.backgroundColor.getColor()));
}
action.setSize(FloatingActionButton.SIZE_MINI);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
action.setCompatElevation(0);
}
return action;
}
@NonNull
private CoordinatorLayout.LayoutParams createActionLayoutParams(int actionIndex) {
CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
lp.setAnchorId(expendedFab.getId());
lp.anchorGravity = Gravity.CENTER_HORIZONTAL;
lp.setBehavior(new ActionBehaviour(expendedFab, (actionIndex + 1) * (actionSize + margin / 2)));
return lp;
}
private static class ActionBehaviour extends CoordinatorLayout.Behavior<FloatingActionButton> {
private final int MAX_VALUE = 90;
private int dependencyId;
private float yStep;
ActionBehaviour(View anchor, float yStep) {
this.yStep = yStep;
this.dependencyId = anchor.getId();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency.getId() == dependencyId;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
final View dependentView = parent.findViewById(dependencyId);
if (dependentView == null) {
return false;
}
final float dependentValue = dependency.getRotation();
float fraction = calculateTransitionFraction(dependentValue);
child.setY(calculateY(dependentView, fraction));
child.setAlpha(calculateAlpha(fraction));
setVisibility(child);
return true;
}
private void setVisibility(FloatingActionButton child) {
child.setVisibility(child.getAlpha() == 0 ? View.GONE : View.VISIBLE);
}
private float calculateAlpha(float fraction) {
return 1 * fraction;
}
private float calculateY(View dependentView, float fraction) {
return dependentView.getY() - yStep * fraction;
}
@FloatRange(from=0.0, to=1.0)
private float calculateTransitionFraction(float dependentValue) {
return 1 - Math.abs(dependentValue / MAX_VALUE);
}
}
}