| // Copyright 2015 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.compositor.bottombar.contextualsearch; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.content.Context; |
| import android.view.ViewGroup; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelAnimation; |
| import org.chromium.chrome.browser.contextualsearch.QuickActionCategory; |
| import org.chromium.chrome.browser.contextualsearch.ResolvedSearchTerm.CardTag; |
| import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; |
| import org.chromium.ui.base.LocalizationUtils; |
| import org.chromium.ui.resources.dynamics.DynamicResourceLoader; |
| |
| /** |
| * Controls the Search Bar in the Contextual Search Panel. |
| * This class holds instances of its subcomponents such as the main text, caption, icon |
| * and interaction controls such as the close box. |
| */ |
| public class ContextualSearchBarControl { |
| /** Full opacity -- fully visible. */ |
| private static final float FULL_OPACITY = 1.0f; |
| |
| /** Transparent opacity -- completely transparent (not visible). */ |
| private static final float TRANSPARENT_OPACITY = 0.0f; |
| |
| /** |
| * The panel used to get information about the panel layout. |
| */ |
| protected ContextualSearchPanel mContextualSearchPanel; |
| |
| /** |
| * The {@link ContextualSearchContextControl} used to control the Search Context View. |
| */ |
| private final ContextualSearchContextControl mContextControl; |
| |
| /** |
| * The {@link ContextualSearchTermControl} used to control the Search Term View. |
| */ |
| private final ContextualSearchTermControl mSearchTermControl; |
| |
| /** |
| * The {@link ContextualSearchCaptionControl} used to control the Caption View. |
| */ |
| private final ContextualSearchCaptionControl mCaptionControl; |
| |
| /** |
| * The {@link ContextualSearchQuickActionControl} used to control quick action behavior. |
| */ |
| private final ContextualSearchQuickActionControl mQuickActionControl; |
| |
| /** |
| * The {@link ContextualSearchCardIconControl} used to control icons for non-action Cards |
| * returned by the server. |
| */ |
| private final ContextualSearchCardIconControl mCardIconControl; |
| |
| /** The width of our icon, including padding, in pixels. */ |
| private final float mPaddedIconWidthPx; |
| |
| /** |
| * The {@link ContextualSearchImageControl} for the panel. |
| */ |
| private ContextualSearchImageControl mImageControl; |
| |
| /** |
| * The opacity of the Bar's Search Context. |
| * This text control may not be initialized until the opacity is set beyond 0. |
| */ |
| private float mSearchBarContextOpacity; |
| |
| /** |
| * The opacity of the Bar's Search Term. |
| * This text control may not be initialized until the opacity is set beyond 0. |
| */ |
| private float mSearchBarTermOpacity; |
| |
| // Dimensions used for laying out the search bar. |
| private final float mTextLayerMinHeight; |
| private final float mTermCaptionSpacing; |
| |
| /** |
| * The visibility percentage for the divider line ranging from 0.f to 1.f. |
| */ |
| private float mDividerLineVisibilityPercentage; |
| |
| /** |
| * The width of the divider line in px. |
| */ |
| private final float mDividerLineWidth; |
| |
| /** |
| * The height of the divider line in px. |
| */ |
| private final float mDividerLineHeight; |
| |
| /** |
| * The divider line color. |
| */ |
| private final int mDividerLineColor; |
| |
| /** |
| * The width of the end button in px. |
| */ |
| private final float mEndButtonWidth; |
| |
| /** |
| * The percentage the panel is expanded. 1.f is fully expanded and 0.f is peeked. |
| */ |
| private float mExpandedPercent; |
| |
| /** |
| * Converts dp dimensions to pixels. |
| */ |
| private final float mDpToPx; |
| |
| /** |
| * Whether the panel contents can be promoted to a new tab. |
| */ |
| private final boolean mCanPromoteToNewTab; |
| |
| /** The animator that controls the text opacity. */ |
| private CompositorAnimator mTextOpacityAnimation; |
| |
| /** The animator that controls the divider line visibility. */ |
| private CompositorAnimator mDividerLineVisibilityAnimation; |
| |
| /** The animator that controls touch highlighting. */ |
| private CompositorAnimator mTouchHighlightAnimation; |
| |
| /** |
| * Constructs a new bottom bar control container by inflating views from XML. |
| * |
| * @param panel The panel. |
| * @param container The parent view for the bottom bar views. |
| * @param loader The resource loader that will handle the snapshot capturing. |
| */ |
| public ContextualSearchBarControl(ContextualSearchPanel panel, |
| Context context, |
| ViewGroup container, |
| DynamicResourceLoader loader) { |
| mContextualSearchPanel = panel; |
| mCanPromoteToNewTab = panel.canPromoteToNewTab(); |
| mImageControl = new ContextualSearchImageControl(panel); |
| mContextControl = new ContextualSearchContextControl(panel, context, container, loader); |
| mSearchTermControl = new ContextualSearchTermControl(panel, context, container, loader); |
| mCaptionControl = new ContextualSearchCaptionControl( |
| panel, context, container, loader, mCanPromoteToNewTab); |
| mQuickActionControl = new ContextualSearchQuickActionControl(context, loader); |
| mCardIconControl = new ContextualSearchCardIconControl(context, loader); |
| |
| mTextLayerMinHeight = context.getResources().getDimension( |
| R.dimen.contextual_search_text_layer_min_height); |
| mTermCaptionSpacing = context.getResources().getDimension( |
| R.dimen.contextual_search_term_caption_spacing); |
| |
| // Divider line values. |
| mDividerLineWidth = context.getResources().getDimension( |
| R.dimen.contextual_search_divider_line_width); |
| mDividerLineHeight = context.getResources().getDimension( |
| R.dimen.contextual_search_divider_line_height); |
| mDividerLineColor = ApiCompatibilityUtils.getColor( |
| context.getResources(), R.color.contextual_search_divider_line_color); |
| |
| // Icon attributes. |
| mPaddedIconWidthPx = |
| context.getResources().getDimension(R.dimen.contextual_search_padded_button_width); |
| mEndButtonWidth = mPaddedIconWidthPx |
| + context.getResources().getDimension(R.dimen.overlay_panel_button_padding); |
| mDpToPx = context.getResources().getDisplayMetrics().density; |
| } |
| |
| /** |
| * @return The {@link ContextualSearchImageControl} for the panel. |
| */ |
| public ContextualSearchImageControl getImageControl() { |
| return mImageControl; |
| } |
| |
| /** |
| * Returns the minimum height that the text layer (containing the Search Context, Term and |
| * Caption) should be. |
| */ |
| public float getTextLayerMinHeight() { |
| return mTextLayerMinHeight; |
| } |
| |
| /** |
| * Returns the spacing that should be placed between the Search Term and Caption. |
| */ |
| public float getSearchTermCaptionSpacing() { |
| return mTermCaptionSpacing; |
| } |
| |
| /** |
| * Removes the bottom bar views from the parent container. |
| */ |
| public void destroy() { |
| mContextControl.destroy(); |
| mSearchTermControl.destroy(); |
| mCaptionControl.destroy(); |
| mQuickActionControl.destroy(); |
| mCardIconControl.destroy(); |
| } |
| |
| /** |
| * Updates this bar when in transition between closed to peeked states. |
| * @param percentage The percentage to the more opened state. |
| */ |
| public void onUpdateFromCloseToPeek(float percentage) { |
| // #onUpdateFromPeekToExpanded() never reaches the 0.f value because this method is called |
| // instead. If the panel is fully peeked, call #onUpdateFromPeekToExpanded(). |
| if (percentage == FULL_OPACITY) onUpdateFromPeekToExpand(TRANSPARENT_OPACITY); |
| |
| // When the panel is completely closed the caption and custom image should be hidden. |
| if (percentage == TRANSPARENT_OPACITY) { |
| mQuickActionControl.reset(); |
| mCaptionControl.hide(); |
| getImageControl().hideCustomImage(false); |
| } |
| } |
| |
| /** |
| * Updates this bar when in transition between peeked to expanded states. |
| * @param percentage The percentage to the more opened state. |
| */ |
| public void onUpdateFromPeekToExpand(float percentage) { |
| mExpandedPercent = percentage; |
| |
| getImageControl().onUpdateFromPeekToExpand(percentage); |
| mCaptionControl.onUpdateFromPeekToExpand(percentage); |
| mSearchTermControl.onUpdateFromPeekToExpand(percentage); |
| mContextControl.onUpdateFromPeekToExpand(percentage); |
| } |
| |
| /** |
| * Sets the details of the context to display in the control. |
| * @param selection The portion of the context that represents the user's selection. |
| * @param end The portion of the context after the selection. |
| */ |
| public void setContextDetails(String selection, String end) { |
| cancelSearchTermResolutionAnimation(); |
| hideCaption(); |
| mQuickActionControl.reset(); |
| mContextControl.setContextDetails(selection, end); |
| resetSearchBarContextOpacity(); |
| } |
| |
| /** |
| * Updates the Bar to display a dictionary definition card. |
| * @param searchTerm The string that represents the search term to display. |
| * @param cardTagEnum Which kind of card is being shown in this update. |
| */ |
| void updateForDictionaryDefinition(String searchTerm, @CardTag int cardTagEnum) { |
| if (!mCardIconControl.didUpdateControlsForDefinition( |
| mContextControl, mImageControl, searchTerm, cardTagEnum)) { |
| // Can't style, just update with the text to display. |
| setSearchTerm(searchTerm); |
| animateSearchTermResolution(); |
| } |
| } |
| |
| /** |
| * Sets the search term to display in the control. |
| * @param searchTerm The string that represents the search term. |
| */ |
| public void setSearchTerm(String searchTerm) { |
| cancelSearchTermResolutionAnimation(); |
| hideCaption(); |
| mQuickActionControl.reset(); |
| mSearchTermControl.setSearchTerm(searchTerm); |
| resetSearchBarTermOpacity(); |
| } |
| |
| /** |
| * Sets the caption to display in the control and sets the caption visible. |
| * @param caption The caption to display. |
| */ |
| public void setCaption(String caption) { |
| mCaptionControl.setCaption(caption); |
| } |
| |
| /** |
| * Gets the current animation percentage for the Caption control, which guides the vertical |
| * position and opacity of the caption. |
| * @return The animation percentage ranging from 0.0 to 1.0. |
| * |
| */ |
| public float getCaptionAnimationPercentage() { |
| return mCaptionControl.getAnimationPercentage(); |
| } |
| |
| /** |
| * @return Whether the caption control is visible. |
| */ |
| public boolean getCaptionVisible() { |
| return mCaptionControl.getIsVisible(); |
| } |
| |
| /** |
| * @return The Id of the Search Context View. |
| */ |
| public int getSearchContextViewId() { |
| return mContextControl.getViewId(); |
| } |
| |
| /** |
| * @return The Id of the Search Term View. |
| */ |
| public int getSearchTermViewId() { |
| return mSearchTermControl.getViewId(); |
| } |
| |
| /** |
| * @return The Id of the Search Caption View. |
| */ |
| public int getCaptionViewId() { |
| return mCaptionControl.getViewId(); |
| } |
| |
| /** |
| * @return The text currently showing in the caption view. |
| */ |
| @VisibleForTesting |
| public CharSequence getCaptionText() { |
| return mCaptionControl.getCaptionText(); |
| } |
| |
| /** |
| * @return The opacity of the SearchBar's search context. |
| */ |
| public float getSearchBarContextOpacity() { |
| return mSearchBarContextOpacity; |
| } |
| |
| /** |
| * @return The opacity of the SearchBar's search term. |
| */ |
| public float getSearchBarTermOpacity() { |
| return mSearchBarTermOpacity; |
| } |
| |
| /** |
| * Sets the quick action if one is available. |
| * @param quickActionUri The URI for the intent associated with the quick action. |
| * @param quickActionCategory The {@link QuickActionCategory} for the quick action. |
| * @param toolbarBackgroundColor The current toolbar background color. This may be used for |
| * icon tinting. |
| */ |
| public void setQuickAction(String quickActionUri, @QuickActionCategory int quickActionCategory, |
| int toolbarBackgroundColor) { |
| mQuickActionControl.setQuickAction( |
| quickActionUri, quickActionCategory, toolbarBackgroundColor); |
| if (mQuickActionControl.hasQuickAction()) { |
| // TODO(twellington): should the quick action caption be stored separately from the |
| // regular caption? |
| mCaptionControl.setCaption(mQuickActionControl.getCaption()); |
| mImageControl.setCardIconResourceId(mQuickActionControl.getIconResId()); |
| } |
| } |
| |
| /** |
| * @return The {@link ContextualSearchQuickActionControl} for the panel. |
| */ |
| public ContextualSearchQuickActionControl getQuickActionControl() { |
| return mQuickActionControl; |
| } |
| |
| /** |
| * Resets the SearchBar text opacity when a new search context is set. The search |
| * context is made visible and the search term invisible. |
| */ |
| private void resetSearchBarContextOpacity() { |
| mSearchBarContextOpacity = FULL_OPACITY; |
| mSearchBarTermOpacity = TRANSPARENT_OPACITY; |
| } |
| |
| /** |
| * Resets the SearchBar text opacity when a new search term is set. The search |
| * term is made visible and the search context invisible. |
| */ |
| private void resetSearchBarTermOpacity() { |
| mSearchBarContextOpacity = TRANSPARENT_OPACITY; |
| mSearchBarTermOpacity = FULL_OPACITY; |
| } |
| |
| /** |
| * Hides the caption so it will not be displayed in the control. |
| */ |
| private void hideCaption() { |
| mCaptionControl.hide(); |
| } |
| |
| // ============================================================================================ |
| // Divider Line |
| // ============================================================================================ |
| /** |
| * @return The visibility percentage for the divider line ranging from 0.f to 1.f. |
| */ |
| public float getDividerLineVisibilityPercentage() { |
| return mDividerLineVisibilityPercentage; |
| } |
| |
| /** |
| * @return The width of the divider line in px. |
| */ |
| public float getDividerLineWidth() { |
| return mDividerLineWidth; |
| } |
| |
| /** |
| * @return The height of the divider line in px. |
| */ |
| public float getDividerLineHeight() { |
| return mDividerLineHeight; |
| } |
| |
| /** |
| * @return The divider line color. |
| */ |
| public int getDividerLineColor() { |
| return mDividerLineColor; |
| } |
| |
| /** |
| * @return The x-offset for the divider line relative to the x-position of the Bar in px. |
| */ |
| public float getDividerLineXOffset() { |
| if (LocalizationUtils.isLayoutRtl()) { |
| return mEndButtonWidth; |
| } else { |
| return mContextualSearchPanel.getContentViewWidthPx() - mEndButtonWidth |
| - getDividerLineWidth(); |
| } |
| } |
| |
| // ============================================================================================ |
| // Touch Highlight |
| // ============================================================================================ |
| |
| /** |
| * Whether the touch highlight is visible. |
| */ |
| private boolean mTouchHighlightVisible; |
| |
| /** |
| * Whether the touch that triggered showing the touch highlight was on the end Bar button. |
| */ |
| private boolean mWasTouchOnEndButton; |
| |
| /** |
| * Whether the divider line was visible when the touch highlight started showing. |
| */ |
| private boolean mWasDividerVisibleOnTouch; |
| |
| /** Where the touch highlight should start, in pixels. */ |
| private float mTouchHighlightXOffsetPx; |
| |
| /** The width of the touch highlight, in pixels. */ |
| private float mTouchHighlightWidthPx; |
| |
| /** |
| * @return Whether the touch highlight is visible. |
| */ |
| public boolean getTouchHighlightVisible() { |
| return mTouchHighlightVisible; |
| } |
| |
| /** |
| * @return The x-offset of the touch highlight in pixels. |
| */ |
| public float getTouchHighlightXOffsetPx() { |
| return mTouchHighlightXOffsetPx; |
| } |
| |
| /** |
| * @return The width of the touch highlight in pixels. |
| */ |
| public float getTouchHighlightWidthPx() { |
| return mTouchHighlightWidthPx; |
| } |
| |
| /** |
| * Should be called when the Bar is clicked. |
| * @param xDps The x-position of the click in DPs. |
| */ |
| public void onSearchBarClick(float xDps) { |
| showTouchHighlight(xDps * mDpToPx); |
| } |
| |
| /** |
| * Should be called when an onShowPress() event occurs on the Bar. |
| * See {@code GestureDetector.SimpleOnGestureListener#onShowPress()}. |
| * @param xDps The x-position of the touch in DPs. |
| */ |
| public void onShowPress(float xDps) { |
| showTouchHighlight(xDps * mDpToPx); |
| } |
| |
| /** |
| * Classifies the give x position in pixels and computes the highlight offset and width. |
| * @param xPx The x-coordinate of a touch location, in pixels. |
| */ |
| private void classifyTouchLocation(float xPx) { |
| // There are 3 cases: |
| // 1) The whole Bar (without any icons) |
| // 2) The Bar minus icon (when the icon is present) |
| // 3) The icon |
| int panelWidth = mContextualSearchPanel.getContentViewWidthPx(); |
| if (mContextualSearchPanel.isPeeking()) { |
| // Case 1 - whole Bar. |
| mTouchHighlightXOffsetPx = 0; |
| mTouchHighlightWidthPx = panelWidth; |
| } else { |
| // The open-tab-icon is on the right (on the left in RTL). |
| boolean isRtl = LocalizationUtils.isLayoutRtl(); |
| float paddedIconWithMarginWidth = |
| (mContextualSearchPanel.getBarMarginSide() |
| + mContextualSearchPanel.getOpenTabIconDimension() |
| + mContextualSearchPanel.getButtonPaddingDps()) |
| * mDpToPx; |
| float contentWidth = panelWidth - paddedIconWithMarginWidth; |
| // Adjust the touch point to panel coordinates. |
| xPx -= mContextualSearchPanel.getOffsetX() * mDpToPx; |
| if (isRtl && xPx > paddedIconWithMarginWidth || !isRtl && xPx < contentWidth) { |
| // Case 2 - Bar minus icon. |
| mTouchHighlightXOffsetPx = isRtl ? paddedIconWithMarginWidth : 0; |
| mTouchHighlightWidthPx = contentWidth; |
| } else { |
| // Case 3 - the icon. |
| mTouchHighlightXOffsetPx = isRtl ? 0 : contentWidth; |
| mTouchHighlightWidthPx = paddedIconWithMarginWidth; |
| } |
| } |
| } |
| |
| /** |
| * Shows the touch highlight if it is not already visible. |
| * @param x The x-position of the touch in px. |
| */ |
| private void showTouchHighlight(float x) { |
| if (mTouchHighlightVisible) return; |
| |
| mWasTouchOnEndButton = isTouchOnEndButton(x); |
| |
| // If the panel is expanded or maximized and the panel content cannot be promoted to a new |
| // tab, then tapping anywhere besides the end buttons does nothing. In this case, the touch |
| // highlight should not be shown. |
| if (!mContextualSearchPanel.isPeeking() && !mCanPromoteToNewTab) return; |
| |
| classifyTouchLocation(x); |
| mTouchHighlightVisible = true; |
| |
| // The touch highlight animation is used to ensure the touch highlight is visible for at |
| // least OverlayPanelAnimation.BASE_ANIMATION_DURATION_MS. |
| // TODO(donnd): Add a material ripple to this animation. |
| if (mTouchHighlightAnimation == null) { |
| mTouchHighlightAnimation = |
| new CompositorAnimator(mContextualSearchPanel.getAnimationHandler()); |
| mTouchHighlightAnimation.setDuration(OverlayPanelAnimation.BASE_ANIMATION_DURATION_MS); |
| mTouchHighlightAnimation.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mTouchHighlightVisible = false; |
| } |
| }); |
| } |
| mTouchHighlightAnimation.cancel(); |
| mTouchHighlightAnimation.start(); |
| } |
| |
| /** |
| * @param xPx The x-position of the touch in px. |
| * @return Whether the touch occurred on the search Bar's end button. |
| */ |
| private boolean isTouchOnEndButton(float xPx) { |
| if (getDividerLineVisibilityPercentage() == TRANSPARENT_OPACITY) return false; |
| |
| // Adjust the touch position to compensate for the narrow panel. |
| xPx -= mContextualSearchPanel.getOffsetX() * mDpToPx; |
| |
| if (LocalizationUtils.isLayoutRtl()) return xPx <= getDividerLineXOffset(); |
| return xPx > getDividerLineXOffset(); |
| } |
| |
| // ============================================================================================ |
| // Search Bar Animation |
| // ============================================================================================ |
| |
| /** |
| * Animates the search term resolution. |
| */ |
| public void animateSearchTermResolution() { |
| if (mTextOpacityAnimation == null) { |
| mTextOpacityAnimation = CompositorAnimator.ofFloat( |
| mContextualSearchPanel.getAnimationHandler(), TRANSPARENT_OPACITY, FULL_OPACITY, |
| OverlayPanelAnimation.BASE_ANIMATION_DURATION_MS, null); |
| mTextOpacityAnimation.addUpdateListener( |
| animator -> updateSearchBarTextOpacity(animator.getAnimatedValue())); |
| } |
| mTextOpacityAnimation.cancel(); |
| mTextOpacityAnimation.start(); |
| } |
| |
| /** |
| * Cancels the search term resolution animation if it is in progress. |
| */ |
| public void cancelSearchTermResolutionAnimation() { |
| if (mTextOpacityAnimation != null) mTextOpacityAnimation.cancel(); |
| } |
| |
| /** |
| * Updates the UI state for the SearchBar text. The search context view will fade out |
| * while the search term fades in. |
| * |
| * @param percentage The visibility percentage of the search term view. |
| */ |
| private void updateSearchBarTextOpacity(float percentage) { |
| // The search context will start fading out before the search term starts fading in. |
| // They will both be partially visible for overlapPercentage of the animation duration. |
| float overlapPercentage = .75f; |
| float fadingOutPercentage = |
| Math.max(1 - (percentage / overlapPercentage), TRANSPARENT_OPACITY); |
| float fadingInPercentage = |
| Math.max(percentage - (1 - overlapPercentage), TRANSPARENT_OPACITY) |
| / overlapPercentage; |
| |
| mSearchBarContextOpacity = fadingOutPercentage; |
| mSearchBarTermOpacity = fadingInPercentage; |
| } |
| } |