/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.waz.zclient.ui.optionsmenu;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.waz.api.IConversation;
import com.waz.zclient.ui.R;
import com.waz.zclient.ui.animation.interpolators.penner.Expo;
import com.waz.zclient.ui.animation.interpolators.penner.Quart;
import com.waz.zclient.ui.text.GlyphTextView;
import com.waz.zclient.ui.text.TypefaceTextView;
import com.waz.zclient.ui.theme.OptionsDarkTheme;
import com.waz.zclient.ui.theme.OptionsLightTheme;
import com.waz.zclient.ui.theme.OptionsTheme;
import com.waz.zclient.ui.views.UserDetailsView;
import com.waz.zclient.utils.ViewUtils;
import java.util.Collections;
import java.util.List;
public class OptionsMenu extends FrameLayout implements View.OnClickListener {
private static final int MAX_ITEMS_PER_ROW = 3;
private static final int TOTAL_ITEMS_FOUR = 4;
private static final int TOTAL_ITEMS_FIVE = 5;
/**
* QuartOut out interpolator used in several locations.
*/
private Quart.EaseOut quartOut;
/**
* Expo out interpolator used in several locations.
*/
private Expo.EaseOut expoOut;
/**
* Expo in interpolator used in several locations.
*/
private Expo.EaseIn expoIn;
/**
* The draggable menu.
*/
private LinearLayout menuLayout;
/**
* The title of the settings box.
*/
private TypefaceTextView titleTextView;
private UserDetailsView userDetailsView;
/**
* The cancel button of the settings box.
*/
private TypefaceTextView cancelView;
/**
* The overlay of the settingsbox.
*/
private View backgroundView;
/**
* The state of the settings box.
*/
private State state;
/**
* Observer of the settings box actions.
*/
private Callback callback;
public OptionsMenu(Context context) {
this(context, null);
}
public OptionsMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OptionsMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attributeSet) {
expoOut = new Expo.EaseOut();
expoIn = new Expo.EaseIn();
quartOut = new Quart.EaseOut();
notifyOptionsMenuStateHasChanged(State.CLOSED);
setVisibility(View.GONE);
}
/**
* Open the settings box with an animation.
*/
public void open() {
if (state != State.CLOSED) {
return;
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
animateOpen();
}
}, getResources().getInteger(R.integer.wire__animation__delay__very_short));
}
private void animateOpen() {
setOnClickListener(this);
setVisibility(View.VISIBLE);
notifyOptionsMenuStateHasChanged(State.OPENING);
//animate menu
int menuHeight = menuLayout.getMeasuredHeight();
menuLayout.setTranslationY(menuHeight);
menuLayout.animate()
.withEndAction(new Runnable() {
@Override
public void run() {
onAnimationEnded();
}
})
.translationY(0)
.setStartDelay(getResources().getInteger(R.integer.wire__animation__delay__short))
.setDuration(getResources().getInteger(R.integer.wire__animation__duration__medium))
.setInterpolator(expoOut);
// animate background
backgroundView.setAlpha(0);
backgroundView.animate()
.setStartDelay(getResources().getInteger(R.integer.wire__animation__delay__short))
.setDuration(getResources().getInteger(R.integer.wire__animation__duration__regular))
.setInterpolator(quartOut)
.alpha(1);
// animate title
titleTextView.setAlpha(0);
titleTextView.animate()
.setStartDelay(getResources().getInteger(R.integer.wire__animation__delay__regular))
.setDuration(getResources().getInteger(R.integer.wire__animation__duration__medium))
.setInterpolator(quartOut)
.alpha(1);
userDetailsView.setAlpha(0);
userDetailsView.animate()
.setStartDelay(getResources().getInteger(R.integer.wire__animation__delay__regular))
.setDuration(getResources().getInteger(R.integer.wire__animation__duration__medium))
.setInterpolator(quartOut)
.alpha(1);
}
/**
* Close the settings box with an animation.
*/
public boolean close() {
if (state != State.OPEN) {
return false;
}
setOnClickListener(null);
notifyOptionsMenuStateHasChanged(State.CLOSING);
int duration = getResources().getInteger(R.integer.wire__animation__duration__regular);
int menuHeight = menuLayout.getMeasuredHeight();
menuLayout.animate()
.withEndAction(new Runnable() {
@Override
public void run() {
onAnimationEnded();
}
})
.translationY(menuHeight)
.setInterpolator(expoIn)
.setDuration(duration);
// animate title
titleTextView.animate()
.alpha(0)
.setInterpolator(quartOut)
.setDuration(duration);
userDetailsView.animate()
.alpha(0)
.setInterpolator(quartOut)
.setDuration(duration);
// animate background
backgroundView.animate()
.alpha(0)
.setInterpolator(quartOut)
.setStartDelay(getResources().getInteger(R.integer.wire__animation__delay__long))
.setDuration(getResources().getInteger(R.integer.wire__animation__duration__medium));
return true;
}
public void setBackgroundColor(int color) {
backgroundView.setBackgroundColor(color);
}
public void setMenuItems(List<OptionsMenuItem> optionsMenuItems, @NonNull final OptionsTheme optionsTheme) {
titleTextView.setTextColor(optionsTheme.getTextColorPrimary());
backgroundView.setBackgroundColor(optionsTheme.getOverlayColor());
//important that items are in order
Collections.sort(optionsMenuItems);
// add regular items
menuLayout.removeAllViews();
final int rows = (int) Math.ceil(optionsMenuItems.size() / 3f);
int itemNumber = 0;
for (int i = 0; i < rows; i++) {
LinearLayout row = new LinearLayout(getContext());
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
//int sideMargin = getResources().getDimensionPixelSize(R.dimen.options_menu_row_side_margin);
int topMargin = getResources().getDimensionPixelSize(R.dimen.options_menu_row_top_margin);
params.setMargins(0, topMargin, 0, 0);
row.setLayoutParams(params);
while (row.getChildCount() < MAX_ITEMS_PER_ROW) {
if (itemNumber >= optionsMenuItems.size()) {
break;
}
//logic for keeping the first row at 2 items if there are 4/5 total
if ((optionsMenuItems.size() == TOTAL_ITEMS_FIVE || optionsMenuItems.size() == TOTAL_ITEMS_FOUR) &&
i == 0 && row.getChildCount() == MAX_ITEMS_PER_ROW - 1) {
break;
}
final OptionsMenuItem item = optionsMenuItems.get(itemNumber);
final View optionsMenuItemContainer = LayoutInflater.from(getContext()).inflate(R.layout.options_menu__item, this, false);
final FrameLayout optionsMenuButton = ViewUtils.getView(optionsMenuItemContainer, R.id.fl_options_menu_button);
final GlyphTextView optionsMenuItemGlyph = ViewUtils.getView(optionsMenuItemContainer, R.id.gtv__options_menu_button__glyph);
optionsMenuItemGlyph.setText(item.resGlyphId);
final TextView optionsMenuItemText = ViewUtils.getView(optionsMenuItemContainer, R.id.ttv__settings_box__item);
optionsMenuItemText.setText(item.resTextId);
optionsMenuItemText.setTextColor(optionsTheme.getTextColorPrimary());
if (item.isToggled()) {
if (optionsTheme.getType() == OptionsTheme.Type.DARK) {
optionsMenuItemGlyph.setTextColor(new OptionsLightTheme(getContext()).getTextColorPrimarySelector());
optionsMenuButton.setBackground(getResources().getDrawable(R.drawable.selector__icon_button__background__dark_toggled));
} else {
optionsMenuItemGlyph.setTextColor(new OptionsDarkTheme(getContext()).getTextColorPrimarySelector());
optionsMenuButton.setBackground(getResources().getDrawable(R.drawable.selector__icon_button__background__light_toggled));
}
} else {
if (optionsTheme.getType() == OptionsTheme.Type.DARK) {
optionsMenuButton.setBackground(getResources().getDrawable(R.drawable.selector__icon_button__background__dark));
} else {
optionsMenuButton.setBackground(getResources().getDrawable(R.drawable.selector__icon_button__background__light));
}
optionsMenuItemGlyph.setTextColor(optionsTheme.getTextColorPrimarySelector());
}
optionsMenuItemText.setTextColor(optionsTheme.getTextColorPrimarySelector());
optionsMenuItemContainer.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
notifyOptionsMenuItemClicked(item);
}
});
optionsMenuItemContainer.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return notifyOptionsMenuItemLongClicked(item);
}
});
row.addView(optionsMenuItemContainer);
itemNumber++;
}
menuLayout.addView(row);
}
//set cancel button
cancelView.setText(getResources().getString(R.string.confirmation_menu__cancel));
cancelView.setTextColor(optionsTheme.getTextColorPrimarySelector());
cancelView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
close();
}
});
menuLayout.addView(cancelView);
notifyOptionsMenuStateHasChanged(State.CLOSED);
setVisibility(View.INVISIBLE);
}
/**
* Adds an observer to be notified.
*/
public void setCallback(Callback callback) {
this.callback = callback;
}
/**
* Sets the context title.
*/
public void setTitle(String title) {
titleTextView.setText(title);
}
public void setConversationDetails(IConversation conversation) {
if (conversation.getType() == IConversation.Type.GROUP ||
conversation.getType() == IConversation.Type.UNKNOWN) {
userDetailsView.setUser(null);
return;
}
userDetailsView.setUser(conversation.getOtherParticipant());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
LayoutInflater.from(getContext()).inflate(R.layout.options_menu, this);
titleTextView = ViewUtils.getView(this, R.id.ttv__settings_box__title);
userDetailsView = ViewUtils.getView(this, R.id.udv__settings_box__user_details);
cancelView = ViewUtils.getView(this, R.id.ttv__settings_box__cancel_button);
if (titleTextView == null) {
throw new IllegalStateException("A typeface text view view needs to be provided in the xml layout.");
}
menuLayout = ViewUtils.getView(this, R.id.ll__settings_box__container);
if (menuLayout == null) {
throw new IllegalStateException("A typeface text view view needs to be provided in the xml layout.");
}
backgroundView = ViewUtils.getView(this, R.id.v__options_menu__overlay);
}
/**
* Notifies observer that settings box item was clicked.
*/
private void notifyOptionsMenuItemClicked(OptionsMenuItem optionsMenuItem) {
if (callback != null) {
callback.onOptionsMenuItemClicked(optionsMenuItem);
}
}
/**
* Notifies observer that settings box item was long clicked
*/
private boolean notifyOptionsMenuItemLongClicked(OptionsMenuItem optionsMenuItem) {
if (callback != null) {
return callback.onOptionsMenuItemLongClicked(optionsMenuItem);
}
return false;
}
/**
* Notifies observer that the state of the settings box has changed.
*
* @param state
*/
private void notifyOptionsMenuStateHasChanged(State state) {
if (callback != null) {
callback.onOptionsMenuStateHasChanged(state);
}
this.state = state;
}
private void onAnimationEnded() {
if (state == State.CLOSING) {
setVisibility(View.GONE);
notifyOptionsMenuStateHasChanged(State.CLOSED);
} else if (state == State.OPENING) {
notifyOptionsMenuStateHasChanged(State.OPEN);
}
}
@Override
public void onClick(View view) {
close();
}
/**
* Observer interface that needs to be implemented by parent layout or fragment.
*/
public interface Callback {
void onOptionsMenuStateHasChanged(State state);
void onOptionsMenuItemClicked(OptionsMenuItem optionsMenuItem);
boolean onOptionsMenuItemLongClicked(OptionsMenuItem optionsMenuItem);
}
public enum State {
OPEN,
OPENING,
CLOSING,
CLOSED
}
}