Commit a5f4416e authored by Mei Liang's avatar Mei Liang Committed by Commit Bot

[SuggestionUI] Add suggestion card view

This CL adds a TabGridMessageCardView for tab switcher and registers
the item view if the close tab suggestion feature flag is enabled.

TabGridMessageCardView is not used anywhere in the code other than
the tests yet.

Change-Id: Ib06d6c45070c3e24e64ac3592d698ff610f634b9
Bug: 1004570
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1808559
Commit-Queue: Mei Liang <meiliang@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703558}
parent 7dc34585
......@@ -102,6 +102,9 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelToolbarCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridMessageCardViewProperties.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridMessageCardView.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridMessageCardViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetContent.java",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 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. -->
<org.chromium.chrome.browser.tasks.tab_management.TabGridMessageCardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tab_grid_iph_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/popup_bg_tinted">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/icon"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:minWidth="8dp"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:src="@drawable/ic_omnibox_page"/>
<org.chromium.chrome.browser.snackbar.TemplatePreservingTextView
android:id="@+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:layout_weight="4"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.BlackBodyDefault" />
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextButton"
android:layout_gravity="center"/>
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/close_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
style="@style/BottomToolbarButton"
android:contentDescription="@string/close"
android:tint="@color/default_icon_color" />
</org.chromium.chrome.browser.tasks.tab_management.TabGridMessageCardView>
// Copyright 2019 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.tasks.tab_management;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import org.chromium.chrome.browser.snackbar.TemplatePreservingTextView;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.widget.ButtonCompat;
import org.chromium.ui.widget.ChromeImageView;
import java.lang.ref.WeakReference;
/**
* Represents a secondary card view in Grid Tab Switcher. The view contains an icon, a description,
* an action button for acceptance, and a close button for dismissal.
*/
class TabGridMessageCardView extends LinearLayout {
private static WeakReference<Bitmap> sCloseButtonBitmapWeakRef;
private ChromeImageView mIcon;
private TemplatePreservingTextView mDescription;
private ButtonCompat mActionButton;
private ChromeImageView mCloseButton;
public TabGridMessageCardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIcon = findViewById(R.id.icon);
mDescription = findViewById(R.id.description);
mActionButton = findViewById(R.id.action_button);
mCloseButton = findViewById(R.id.close_button);
if (sCloseButtonBitmapWeakRef == null || sCloseButtonBitmapWeakRef.get() == null) {
int closeButtonSize =
(int) getResources().getDimension(R.dimen.tab_grid_close_button_size);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.btn_close);
sCloseButtonBitmapWeakRef = new WeakReference<>(
Bitmap.createScaledBitmap(bitmap, closeButtonSize, closeButtonSize, true));
}
mCloseButton.setImageBitmap(sCloseButtonBitmapWeakRef.get());
}
/**
* @see TemplatePreservingTextView#setTemplate(String), setDescriptionText() must be called
* after calling this method for the new template text to take effect.
*/
void setDescriptionTextTemplate(String template) {
mDescription.setTemplate(template);
}
/**
* @see TemplatePreservingTextView#setText(CharSequence).
*/
void setDescriptionText(CharSequence text) {
mDescription.setText(text);
}
/**
* Set action text for the action button.
* @param actionText Text to be displayed.
*/
void setAction(String actionText) {
mActionButton.setText(actionText);
}
/**
* Set icon drawable.
* @param iconDrawable Drawable to be shown.
*/
void setIcon(Drawable iconDrawable) {
mIcon.setImageDrawable(iconDrawable);
}
}
// Copyright 2019 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.tasks.tab_management;
import android.view.ViewGroup;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* ViewBinder for TabGridSecondaryItem.
*/
class TabGridMessageCardViewBinder {
public static void bind(PropertyModel model, ViewGroup view, PropertyKey propertyKey) {
assert view instanceof TabGridMessageCardView;
TabGridMessageCardView itemView = (TabGridMessageCardView) view;
if (TabGridMessageCardViewProperties.ACTION_TEXT == propertyKey) {
itemView.setAction(model.get(TabGridMessageCardViewProperties.ACTION_TEXT));
} else if (TabGridMessageCardViewProperties.DESCRIPTION_TEXT == propertyKey) {
itemView.setDescriptionText(
model.get(TabGridMessageCardViewProperties.DESCRIPTION_TEXT));
} else if (TabGridMessageCardViewProperties.DESCRIPTION_TEXT_TEMPLATE == propertyKey) {
itemView.setDescriptionTextTemplate(
model.get(TabGridMessageCardViewProperties.DESCRIPTION_TEXT_TEMPLATE));
}
}
}
\ No newline at end of file
// Copyright 2019 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.tasks.tab_management;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* List of properties used by TabGridSecondaryItem.
*/
class TabGridMessageCardViewProperties {
public static final PropertyModel.WritableObjectPropertyKey<String> ACTION_TEXT =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<String> DESCRIPTION_TEXT =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<String> DESCRIPTION_TEXT_TEMPLATE =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {ACTION_TEXT, DESCRIPTION_TEXT, DESCRIPTION_TEXT_TEMPLATE};
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
......@@ -26,6 +27,10 @@ import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.UiType;
import org.chromium.chrome.tab_ui.R;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
import org.chromium.ui.widget.ViewLookupCachingFrameLayout;
......@@ -59,6 +64,7 @@ public class TabListCoordinator implements Destroyable {
static final int GRID_LAYOUT_SPAN_COUNT_LANDSCAPE = 3;
private final TabListMediator mMediator;
private final TabListRecyclerView mRecyclerView;
private final SimpleRecyclerViewAdapter mAdapter;
private final @TabListMode int mMode;
private final Rect mThumbnailLocationOfCurrentTab = new Rect();
......@@ -100,15 +106,15 @@ public class TabListCoordinator implements Destroyable {
boolean attachToParent, String componentName) {
mMode = mode;
TabListModel modelList = new TabListModel();
SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(modelList);
mAdapter = new SimpleRecyclerViewAdapter(modelList);
RecyclerView.RecyclerListener recyclerListener = null;
if (mMode == TabListMode.GRID || mMode == TabListMode.CAROUSEL) {
adapter.registerType(UiType.SELECTABLE, () -> {
mAdapter.registerType(UiType.SELECTABLE, () -> {
return (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.selectable_tab_grid_card_item, parentView, false);
}, TabGridViewBinder::bindSelectableTab);
adapter.registerType(UiType.CLOSABLE, () -> {
mAdapter.registerType(UiType.CLOSABLE, () -> {
ViewGroup group = (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.closable_tab_grid_card_item, parentView, false);
if (mMode == TabListMode.CAROUSEL) {
......@@ -126,7 +132,7 @@ public class TabListCoordinator implements Destroyable {
thumbnail.setMinimumHeight(thumbnail.getWidth());
};
} else if (mMode == TabListMode.STRIP) {
adapter.registerType(UiType.STRIP, () -> {
mAdapter.registerType(UiType.STRIP, () -> {
return (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.tab_strip_item, parentView, false);
}, TabStripViewBinder::bind);
......@@ -153,7 +159,7 @@ public class TabListCoordinator implements Destroyable {
context.getResources().getDimensionPixelSize(R.dimen.tab_carousel_height);
}
mRecyclerView.setAdapter(adapter);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
if (recyclerListener != null) mRecyclerView.setRecyclerListener(recyclerListener);
......@@ -277,4 +283,15 @@ public class TabListCoordinator implements Destroyable {
long getLastDirtyTimeForTesting() {
return mRecyclerView.getLastDirtyTimeForTesting();
}
/**
* Register a new view type for the component.
* @see MVCListAdapter#registerType(int, MVCListAdapter.ViewBuilder,
* PropertyModelChangeProcessor.ViewBinder).
*/
<T extends View> void registerItemType(@UiType int typeId,
MVCListAdapter.ViewBuilder<T> builder,
PropertyModelChangeProcessor.ViewBinder<PropertyModel, T, PropertyKey> binder) {
mAdapter.registerType(typeId, builder, binder);
}
}
......@@ -23,12 +23,13 @@ import java.lang.annotation.RetentionPolicy;
*/
public class TabProperties {
/** IDs for possible types of UI in the tab list. */
@IntDef({UiType.SELECTABLE, UiType.CLOSABLE, UiType.STRIP})
@IntDef({UiType.SELECTABLE, UiType.CLOSABLE, UiType.STRIP, UiType.SUGGESTION})
@Retention(RetentionPolicy.SOURCE)
public @interface UiType {
int SELECTABLE = 0;
int CLOSABLE = 1;
int STRIP = 2;
int SUGGESTION = 3;
}
public static final PropertyModel.WritableIntPropertyKey TAB_ID =
......
......@@ -7,7 +7,9 @@ package org.chromium.chrome.browser.tasks.tab_management;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
......@@ -17,6 +19,7 @@ import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.MenuOrKeyboardActionController;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
......@@ -134,6 +137,32 @@ public class TabSwitcherCoordinator implements Destroyable, TabSwitcher,
mMediator.setIphProvider(mTabGridIphItemCoordinator.getIphProvider());
}
if (mode == TabListCoordinator.TabListMode.GRID) {
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS)) {
mTabListCoordinator.registerItemType(TabProperties.UiType.SUGGESTION, () -> {
return (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.tab_suggestion_card_item, container, false);
}, TabGridMessageCardViewBinder::bind);
}
assert mTabListCoordinator.getContainerView().getLayoutManager()
instanceof GridLayoutManager;
// TODO(1004570): Have a flexible approach for span size look up for each UiType.
((GridLayoutManager) mTabListCoordinator.getContainerView().getLayoutManager())
.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int itemType = mTabListCoordinator.getContainerView()
.getAdapter()
.getItemViewType(position);
if (itemType == TabProperties.UiType.SUGGESTION) return 2;
return 1;
}
});
}
mMenuOrKeyboardActionController = menuOrKeyboardActionController;
mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(
mTabSwitcherMenuActionHandler);
......
// Copyright 2019 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.tasks.tab_management;
import static org.junit.Assert.assertEquals;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Tests for {@link TabGridMessageCardViewBinder}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
public class TabGridMessageCardViewBinderTest extends DummyUiActivityTestCase {
private static final String ACTION_TEXT = "actionText";
private static final String DESCRIPTION_TEXT = "descriptionText";
private ViewGroup mItemView;
private PropertyModel mItemViewModel;
private PropertyModelChangeProcessor mItemMCP;
@Override
public void setUpTest() throws Exception {
super.setUpTest();
ViewGroup view = new LinearLayout(getActivity());
TestThreadUtils.runOnUiThreadBlocking(() -> {
getActivity().setContentView(view);
mItemView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.tab_suggestion_card_item, null);
view.addView(mItemView);
});
mItemViewModel =
new PropertyModel.Builder(TabGridMessageCardViewProperties.ALL_KEYS)
.with(TabGridMessageCardViewProperties.ACTION_TEXT, ACTION_TEXT)
.with(TabGridMessageCardViewProperties.DESCRIPTION_TEXT, DESCRIPTION_TEXT)
.build();
mItemMCP = PropertyModelChangeProcessor.create(
mItemViewModel, mItemView, TabGridMessageCardViewBinder::bind);
}
private String getDescriptionText() {
return ((TextView) mItemView.findViewById(R.id.description)).getText().toString();
}
@Test
@UiThreadTest
@SmallTest
public void testInitialBinding() {
assertEquals(ACTION_TEXT,
((TextView) mItemView.findViewById(R.id.action_button)).getText().toString());
assertEquals(DESCRIPTION_TEXT, getDescriptionText());
}
@Test
@UiThreadTest
@SmallTest
public void testBindingDescription_WithoutTemplate() {
mItemViewModel.set(TabGridMessageCardViewProperties.DESCRIPTION_TEXT, "test");
assertEquals("test", getDescriptionText());
}
@Test
@UiThreadTest
@SmallTest
public void testBindingDescription_WithTemplate() {
mItemViewModel.set(
TabGridMessageCardViewProperties.DESCRIPTION_TEXT_TEMPLATE, "%s template");
mItemViewModel.set(TabGridMessageCardViewProperties.DESCRIPTION_TEXT, "test");
assertEquals("test template", getDescriptionText());
}
@Override
public void tearDownTest() throws Exception {
mItemMCP.destroy();
super.tearDownTest();
}
}
......@@ -25,6 +25,7 @@ public_tab_management_java_sources += start_surface_public_java_sources
tab_management_test_java_sources = [
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParentTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridMessageCardViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment