blob: 37d0dd8c7ba72114a73cb8c4fb4710a1bed1de04 [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.ntp.snippets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.feed.FeedUma;
import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuButtonDelegate;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.widget.RectProvider;
import org.chromium.ui.widget.ViewRectProvider;
/**
* View for the header of the personalized feed that has a context menu to
* manage the feed.
*/
public class SectionHeaderView extends LinearLayout implements View.OnClickListener {
private static final int IPH_TIMEOUT_MS = 10000;
// Views in the header layout that are set during inflate.
private TextView mTitleView;
private TextView mStatusView;
private ListMenuButton mMenuView;
// Properties that are set after construction & inflate using setters.
@Nullable
private SectionHeader mHeader;
private boolean mHasMenu;
private boolean mHairlineWhenDisabled = true;
public SectionHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray attrArray = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.SectionHeaderView, 0, 0);
try {
mHairlineWhenDisabled = attrArray.getBoolean(
R.styleable.SectionHeaderView_showHairlineWhenDisabled, true);
} finally {
attrArray.recycle();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitleView = findViewById(R.id.header_title);
mStatusView = findViewById(R.id.header_status);
mMenuView = findViewById(R.id.header_menu);
// Use the menu instead of the status text when the menu is available from the inflated
// layout.
mHasMenu = mMenuView != null;
if (mHasMenu) {
mMenuView.setOnClickListener((View v) -> { displayMenu(); });
}
}
@Override
public void onClick(View view) {
assert mHeader.isExpandable() : "onClick() is called on a non-expandable section header.";
mHeader.toggleHeader();
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_TOGGLED_FEED);
SuggestionsMetrics.recordExpandableHeaderTapped(mHeader.isExpanded());
SuggestionsMetrics.recordArticlesListVisible();
}
/** @param header The {@link SectionHeader} that holds the data for this class. */
public void setHeader(SectionHeader header) {
mHeader = header;
if (mHeader == null) return;
// Set visuals with the menu view when present.
if (mHasMenu) {
updateVisuals();
return;
}
// Set visuals with the status view when no menu.
mStatusView.setVisibility(mHeader.isExpandable() ? View.VISIBLE : View.GONE);
updateVisuals();
setOnClickListener(mHeader.isExpandable() ? this : null);
}
/** Update the header view based on whether the header is expanded and its text contents. */
public void updateVisuals() {
if (mHeader == null) return;
mTitleView.setText(mHeader.getHeaderText());
if (mHeader.isExpandable()) {
if (!mHasMenu) {
mStatusView.setText(
mHeader.isExpanded() ? R.string.hide_content : R.string.show_content);
}
setBackgroundResource(mHeader.isExpanded() || !mHairlineWhenDisabled
? 0
: R.drawable.hairline_border_card_background);
}
}
/** Shows an IPH on the feed header menu button. */
public void showMenuIph(UserEducationHelper helper) {
final ViewRectProvider rectProvider = new ViewRectProvider(mMenuView) {
// ViewTreeObserver.OnPreDrawListener implementation.
@Override
public boolean onPreDraw() {
boolean result = super.onPreDraw();
int minRectBottomPosPx =
getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow)
+ mMenuView.getHeight() / 2;
// Notify that the rectangle is hidden to dismiss the popup if the anchor is
// positioned too high.
if (getRect().bottom < minRectBottomPosPx) {
notifyRectHidden();
}
return result;
}
};
helper.requestShowIPH(new IPHCommandBuilder(mMenuView.getContext().getResources(),
FeatureConstants.FEED_HEADER_MENU_FEATURE, R.string.ntp_feed_menu_iph,
R.string.accessibility_ntp_feed_menu_iph)
.setAnchorView(mMenuView)
.setCircleHighlight(true)
.setShouldHighlight(true)
.setDismissOnTouch(false)
.setInsetRect(new Rect(0, 0, 0, 0))
.setAutoDismissTimeout(5 * 1000)
.setViewRectProvider(rectProvider)
.build());
}
private void displayMenu() {
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_CLICKED_FEED_HEADER_MENU);
if (mMenuView == null) {
assert false : "No menu view to display the menu";
return;
}
ModelList listItems = mHeader.getMenuModelList();
if (listItems == null) {
assert false : "No list items model to display the menu";
return;
}
ListMenu.Delegate listMenuDelegate = mHeader.getListMenuDelegate();
if (listMenuDelegate == null) {
assert false : "No list menu delegate for the menu";
return;
}
BasicListMenu listMenu =
new BasicListMenu(mMenuView.getContext(), listItems, listMenuDelegate);
ListMenuButtonDelegate delegate = new ListMenuButtonDelegate() {
@Override
public ListMenu getListMenu() {
return listMenu;
}
@Override
public RectProvider getRectProvider(View listMenuButton) {
ViewRectProvider rectProvider = new ViewRectProvider(listMenuButton);
rectProvider.setIncludePadding(true);
rectProvider.setInsetPx(0, 0, 0, 0);
return rectProvider;
}
};
mMenuView.setDelegate(delegate);
mMenuView.tryToFitLargestItem(true);
mMenuView.showMenu();
}
}