blob: ac66abe41c78d5db14cee7925e4e223c2b9105ba [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.support.annotation.DrawableRes;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper.MenuButtonState;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.widget.PulseDrawable;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
/**
* The overflow menu button.
*/
public class MenuButton extends FrameLayout implements TintObserver {
/** The {@link ImageButton} for the menu button. */
private ImageButton mMenuImageButton;
/** The view for the update badge. */
private ImageView mUpdateBadgeView;
private boolean mUseLightDrawables;
private AppMenuButtonHelper mAppMenuButtonHelper;
private boolean mHighlightingMenu;
private PulseDrawable mHighlightDrawable;
private boolean mSuppressAppMenuUpdateBadge;
private AnimatorSet mMenuBadgeAnimatorSet;
private boolean mIsMenuBadgeAnimationRunning;
/** A provider that notifies components when the theme color changes.*/
private ThemeColorProvider mThemeColorProvider;
/** The menu button text label. */
private TextView mLabel;
/** The wrapper View that contains the menu button and the label. */
private View mWrapper;
public MenuButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuImageButton = findViewById(R.id.menu_button);
mUpdateBadgeView = findViewById(R.id.menu_badge);
}
public void setAppMenuButtonHelper(AppMenuButtonHelper appMenuButtonHelper) {
mAppMenuButtonHelper = appMenuButtonHelper;
View touchView = mWrapper != null ? mWrapper : mMenuImageButton;
if (mWrapper != null) mWrapper.setOnTouchListener(mAppMenuButtonHelper);
mMenuImageButton.setOnTouchListener(mAppMenuButtonHelper);
touchView.setAccessibilityDelegate(mAppMenuButtonHelper);
}
public AppMenuButtonHelper getAppMenuButtonHelper() {
return mAppMenuButtonHelper;
}
public View getMenuBadge() {
return mUpdateBadgeView;
}
public ImageButton getImageButton() {
return mMenuImageButton;
}
/**
* @param wrapper The wrapping View of this button.
*/
public void setWrapperView(ViewGroup wrapper) {
mWrapper = wrapper;
mWrapper.setOnClickListener(null);
mLabel = mWrapper.findViewById(R.id.menu_button_label);
if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
}
/**
* Sets the update badge to visible.
*
* @param visible Whether the update badge should be visible. Always sets visibility to GONE
* if the update type does not require a badge.
* TODO(crbug.com/865801): Clean this up when MenuButton and UpdateMenuItemHelper is MVCed.
*/
private void setUpdateBadgeVisibility(boolean visible) {
mUpdateBadgeView.setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) updateImageResources();
updateContentDescription(visible);
}
private void updateImageResources() {
MenuButtonState buttonState = UpdateMenuItemHelper.getInstance().getUiState().buttonState;
if (buttonState == null) return;
@DrawableRes
int drawable = mUseLightDrawables ? buttonState.lightBadgeIcon : buttonState.darkBadgeIcon;
mUpdateBadgeView.setImageDrawable(
ApiCompatibilityUtils.getDrawable(getResources(), drawable));
}
/**
* Show the update badge on the app menu button.
* @param animate Whether to animate the showing of the update badge.
*/
public void showAppMenuUpdateBadgeIfAvailable(boolean animate) {
if (mUpdateBadgeView == null || mMenuImageButton == null || mSuppressAppMenuUpdateBadge
|| !isBadgeAvailable()) {
return;
}
updateImageResources();
updateContentDescription(true);
if (!animate || mIsMenuBadgeAnimationRunning) {
setUpdateBadgeVisibility(true);
return;
}
// Set initial states.
mUpdateBadgeView.setAlpha(0.f);
mUpdateBadgeView.setVisibility(View.VISIBLE);
mMenuBadgeAnimatorSet = createShowUpdateBadgeAnimation(mMenuImageButton, mUpdateBadgeView);
mMenuBadgeAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mIsMenuBadgeAnimationRunning = true;
}
@Override
public void onAnimationEnd(Animator animation) {
mIsMenuBadgeAnimationRunning = false;
}
@Override
public void onAnimationCancel(Animator animation) {
mIsMenuBadgeAnimationRunning = false;
}
});
mMenuBadgeAnimatorSet.start();
}
/**
* Remove the update badge on the app menu button.
* @param animate Whether to animate the hiding of the update badge.
*/
public void removeAppMenuUpdateBadge(boolean animate) {
if (mUpdateBadgeView == null || !isShowingAppMenuUpdateBadge()) return;
updateContentDescription(false);
if (!animate) {
setUpdateBadgeVisibility(false);
return;
}
if (mIsMenuBadgeAnimationRunning && mMenuBadgeAnimatorSet != null) {
mMenuBadgeAnimatorSet.cancel();
}
// Set initial states.
mMenuImageButton.setAlpha(0.f);
mMenuBadgeAnimatorSet = createHideUpdateBadgeAnimation(mMenuImageButton, mUpdateBadgeView);
mMenuBadgeAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mIsMenuBadgeAnimationRunning = true;
}
@Override
public void onAnimationEnd(Animator animation) {
mIsMenuBadgeAnimationRunning = false;
}
@Override
public void onAnimationCancel(Animator animation) {
mIsMenuBadgeAnimationRunning = false;
}
});
mMenuBadgeAnimatorSet.start();
}
/**
* @param suppress Whether to prevent the update badge from being show. This is currently only
* used to prevent the badge from being shown in the tablet tab switcher.
*/
public void setAppMenuUpdateBadgeSuppressed(boolean suppress) {
mSuppressAppMenuUpdateBadge = suppress;
if (mSuppressAppMenuUpdateBadge) {
removeAppMenuUpdateBadge(false);
} else {
showAppMenuUpdateBadgeIfAvailable(false);
}
}
/**
* @return Whether the update badge is showing.
*/
public boolean isShowingAppMenuUpdateBadge() {
return mUpdateBadgeView.getVisibility() == View.VISIBLE;
}
private static boolean isBadgeAvailable() {
return UpdateMenuItemHelper.getInstance().getUiState().buttonState != null;
}
/**
* Sets the content description for the menu button.
* @param isUpdateBadgeVisible Whether the update menu badge is visible.
*/
private void updateContentDescription(boolean isUpdateBadgeVisible) {
if (isUpdateBadgeVisible) {
MenuButtonState buttonState =
UpdateMenuItemHelper.getInstance().getUiState().buttonState;
assert buttonState != null : "No button state when trying to show the badge.";
mMenuImageButton.setContentDescription(
getResources().getString(buttonState.menuContentDescription));
} else {
mMenuImageButton.setContentDescription(
getResources().getString(R.string.accessibility_toolbar_btn_menu));
}
}
/**
* Sets the menu button's background depending on whether or not we are highlighting and whether
* or not we are using light or dark assets.
*/
public void setMenuButtonHighlightDrawable() {
// Return if onFinishInflate didn't finish
if (mMenuImageButton == null) return;
if (mHighlightingMenu) {
if (mHighlightDrawable == null) {
mHighlightDrawable = PulseDrawable.createCircle(getContext());
mHighlightDrawable.setInset(ViewCompat.getPaddingStart(mMenuImageButton),
mMenuImageButton.getPaddingTop(),
ViewCompat.getPaddingEnd(mMenuImageButton),
mMenuImageButton.getPaddingBottom());
}
mHighlightDrawable.setUseLightPulseColor(
getContext().getResources(), mUseLightDrawables);
setBackground(mHighlightDrawable);
mHighlightDrawable.start();
} else {
setBackground(null);
}
}
public void setMenuButtonHighlight(boolean highlight) {
mHighlightingMenu = highlight;
setMenuButtonHighlightDrawable();
}
public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
mThemeColorProvider = themeColorProvider;
mThemeColorProvider.addTintObserver(this);
}
@Override
public void onTintChanged(ColorStateList tintList, boolean useLight) {
ApiCompatibilityUtils.setImageTintList(mMenuImageButton, tintList);
mUseLightDrawables = useLight;
updateImageResources();
if (mLabel != null) mLabel.setTextColor(tintList);
}
public void destroy() {
if (mThemeColorProvider != null) {
mThemeColorProvider.removeTintObserver(this);
mThemeColorProvider = null;
}
}
/**
* Creates an {@link AnimatorSet} for showing the update badge that is displayed on top
* of the app menu button.
*
* @param menuButton The {@link View} containing the app menu button.
* @param menuBadge The {@link View} containing the update badge.
* @return An {@link AnimatorSet} to run when showing the update badge.
*/
private static AnimatorSet createShowUpdateBadgeAnimation(
final View menuButton, final View menuBadge) {
// Create badge ObjectAnimators.
ObjectAnimator badgeFadeAnimator = ObjectAnimator.ofFloat(menuBadge, View.ALPHA, 1.f);
badgeFadeAnimator.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
int pixelTranslation = menuBadge.getResources().getDimensionPixelSize(
R.dimen.menu_badge_translation_y_distance);
ObjectAnimator badgeTranslateYAnimator =
ObjectAnimator.ofFloat(menuBadge, View.TRANSLATION_Y, pixelTranslation, 0.f);
badgeTranslateYAnimator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
// Create menu button ObjectAnimator.
ObjectAnimator menuButtonFadeAnimator = ObjectAnimator.ofFloat(menuButton, View.ALPHA, 0.f);
menuButtonFadeAnimator.setInterpolator(new LinearInterpolator());
// Create AnimatorSet and listeners.
AnimatorSet set = new AnimatorSet();
set.playTogether(badgeFadeAnimator, badgeTranslateYAnimator, menuButtonFadeAnimator);
set.setDuration(350);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Make sure the menu button is visible again.
menuButton.setAlpha(1.f);
}
@Override
public void onAnimationCancel(Animator animation) {
// Jump to the end state if the animation is canceled.
menuBadge.setAlpha(1.f);
menuBadge.setTranslationY(0.f);
menuButton.setAlpha(1.f);
}
});
return set;
}
/**
* Creates an {@link AnimatorSet} for hiding the update badge that is displayed on top
* of the app menu button.
*
* @param menuButton The {@link View} containing the app menu button.
* @param menuBadge The {@link View} containing the update badge.
* @return An {@link AnimatorSet} to run when hiding the update badge.
*/
private static AnimatorSet createHideUpdateBadgeAnimation(
final View menuButton, final View menuBadge) {
// Create badge ObjectAnimator.
ObjectAnimator badgeFadeAnimator = ObjectAnimator.ofFloat(menuBadge, View.ALPHA, 0.f);
badgeFadeAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
// Create menu button ObjectAnimator.
ObjectAnimator menuButtonFadeAnimator = ObjectAnimator.ofFloat(menuButton, View.ALPHA, 1.f);
menuButtonFadeAnimator.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
// Create AnimatorSet and listeners.
AnimatorSet set = new AnimatorSet();
set.playTogether(badgeFadeAnimator, menuButtonFadeAnimator);
set.setDuration(200);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
menuBadge.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
// Jump to the end state if the animation is canceled.
menuButton.setAlpha(1.f);
menuBadge.setVisibility(View.GONE);
}
});
return set;
}
}