Commit 76d61d37 authored by dgn's avatar dgn Committed by Commit bot

[NTP Client][Reland]Use the separate button style for the NoArticles status

Original issue's description:
> [NTP Client] Use the separate button style for the NoArticles status
>
> Makes the articles and bookmarks sections use the same style of status
> card when they have no snippets.
>
> the hasMoreButton property of categories now only determines whether
> the action item will be shown when there are suggestions to display.
>
> This patch also lets the SuggestionsCategoryInfo be aware of the
> current category it is describing, and moves various category specific
> behaviours into the SuggestionsCategoryInfo class.
>
> Preview: https://goo.gl/photos/VhceT6cjvME6QS8m7
>
> BUG=649670
>
> Committed: https://crrev.com/ffa8e3905fed0b1df0d56ab6e4b17791b31cd171
> Cr-Commit-Position: refs/heads/master@{#423517}

> Revert Data:
> Committed: https://crrev.com/b5e8956c2c8a10484d10a117b3ef5a33ee58cb6c
> Cr-Commit-Position: refs/heads/master@{#423573}

BUG=649670

Review-Url: https://codereview.chromium.org/2401643004
Cr-Commit-Position: refs/heads/master@{#423863}
parent 12596183
......@@ -6,11 +6,9 @@ package org.chromium.chrome.browser.ntp.cards;
import android.view.View;
import org.chromium.base.Log;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
import org.chromium.chrome.browser.ntp.UiConfig;
import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
/**
* Item that allows the user to perform an action on the NTP.
......@@ -18,15 +16,15 @@ import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
class ActionItem implements NewTabPageItem {
private static final String TAG = "NtpCards";
private final int mCategory;
private final SuggestionsCategoryInfo mCategoryInfo;
// The position (index) of this item within its section, for logging purposes.
private int mPosition;
private boolean mImpressionTracked = false;
private boolean mDismissable;
public ActionItem(int category) {
mCategory = category;
public ActionItem(SuggestionsCategoryInfo categoryInfo) {
mCategoryInfo = categoryInfo;
}
@Override
......@@ -45,31 +43,19 @@ class ActionItem implements NewTabPageItem {
public static class ViewHolder extends CardViewHolder {
private ActionItem mActionListItem;
public ViewHolder(NewTabPageRecyclerView recyclerView, final NewTabPageManager manager,
UiConfig uiConfig) {
public ViewHolder(final NewTabPageRecyclerView recyclerView,
final NewTabPageManager manager, UiConfig uiConfig) {
super(R.layout.new_tab_page_action_card, recyclerView, uiConfig);
itemView.findViewById(R.id.action_button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int category = mActionListItem.mCategory;
int category = mActionListItem.mCategoryInfo.getCategory();
manager.trackSnippetCategoryActionClick(
category, mActionListItem.mPosition);
if (category == KnownCategories.BOOKMARKS) {
manager.navigateToBookmarks();
} else if (category == KnownCategories.DOWNLOADS) {
manager.navigateToDownloadManager();
} else if (category == KnownCategories.FOREIGN_TABS) {
manager.navigateToRecentTabs();
} else {
// TODO(pke): This should redirect to the C++ backend. Once it does,
// change the condition in the SuggestionsSection constructor.
Log.wtf(TAG,
"More action called on a dynamically added section: %d",
category);
}
mActionListItem.mCategoryInfo.performEmptyStateAction(
manager, recyclerView.getNewTabPageAdapter());
}
});
......@@ -79,7 +65,8 @@ class ActionItem implements NewTabPageItem {
if (mActionListItem != null && !mActionListItem.mImpressionTracked) {
mActionListItem.mImpressionTracked = true;
manager.trackSnippetCategoryActionImpression(
mActionListItem.mCategory, mActionListItem.mPosition);
mActionListItem.mCategoryInfo.getCategory(),
mActionListItem.mPosition);
}
}
});
......
......@@ -211,7 +211,7 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder>
SuggestionsSection section = mSections.get(category);
if (section == null) {
section = new SuggestionsSection(category, info, this);
section = new SuggestionsSection(info, this);
mSections.put(category, section);
}
......
......@@ -5,118 +5,33 @@
package org.chromium.chrome.browser.ntp.cards;
import android.content.Context;
import android.support.annotation.Nullable;
import org.chromium.base.Log;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum;
/**
* Card that is shown when the user needs to be made aware of some information about their
* configuration about the NTP suggestions: there is no more available suggested content, sync
* should be enabled, etc.
* configuration that affects the NTP suggestions.
*/
public abstract class StatusItem implements NewTabPageItem {
/**
* Delegate to provide the status cards a way to interact with the rest of the system: tap
* handler, etc.
*/
public interface ActionDelegate { void onButtonTapped(); }
private static class NoBookmarks extends StatusItem {
public NoBookmarks() {
super(R.string.ntp_status_card_title_no_bookmarks,
R.string.ntp_status_card_no_bookmarks, 0);
Log.d(TAG, "Registering card for status: No Bookmarks");
}
@Override
protected void performAction(Context context) {
assert false; // not reached.
}
@Override
protected boolean hasAction() {
return false;
}
}
private static class NoSnippets extends StatusItem {
private final ActionDelegate mActionDelegate;
public NoSnippets(ActionDelegate actionDelegate) {
super(R.string.ntp_status_card_title_no_articles, R.string.ntp_status_card_no_articles,
R.string.reload);
mActionDelegate = actionDelegate;
Log.d(TAG, "Registering card for status: No Snippets");
}
@Override
protected void performAction(Context context) {
mActionDelegate.onButtonTapped();
}
}
private static final String TAG = "NtpCards";
public class StatusItem implements NewTabPageItem {
private final int mHeaderStringId;
private final int mDescriptionStringId;
private final int mActionStringId;
public static StatusItem create(
@CategoryStatusEnum int categoryStatus, @Nullable ActionDelegate actionDelegate) {
switch (categoryStatus) {
case CategoryStatus.SIGNED_OUT:
// Fall through. Sign out is a transitive state that we just use to clear content.
case CategoryStatus.AVAILABLE:
case CategoryStatus.AVAILABLE_LOADING:
case CategoryStatus.INITIALIZING:
// TODO(dgn): rewrite this whole thing? Get one card and change its state instead
// of recreating it. It would be more flexible in terms of adapting the content
// to different usages.
return actionDelegate == null ? new NoBookmarks() : new NoSnippets(actionDelegate);
case CategoryStatus.ALL_SUGGESTIONS_EXPLICITLY_DISABLED:
Log.wtf(TAG, "Attempted to create a status card while the feature should be off.");
return null;
case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
// In this case, the entire section should have been cleared off the UI.
Log.wtf(TAG, "Attempted to create a status card for content suggestions "
+ " when the category status is CATEGORY_EXPLICITLY_DISABLED.");
return null;
case CategoryStatus.NOT_PROVIDED:
// In this case, the UI should remain as it is and also keep the previous category
// status, so the NOT_PROVIDED should never reach here.
Log.wtf(TAG, "Attempted to create a status card for content suggestions "
+ " when the category is NOT_PROVIDED.");
return null;
case CategoryStatus.LOADING_ERROR:
// In this case, the entire section should have been cleared off the UI.
Log.wtf(TAG, "Attempted to create a status card for content suggestions "
+ " when the category is LOADING_ERROR.");
return null;
default:
Log.wtf(TAG, "Attempted to create a status card for an unknown value: %d",
categoryStatus);
return null;
}
}
protected StatusItem(int headerStringId, int descriptionStringId, int actionStringId) {
mHeaderStringId = headerStringId;
mDescriptionStringId = descriptionStringId;
mActionStringId = actionStringId;
}
protected abstract void performAction(Context context);
public static StatusItem createNoSuggestionsItem(SuggestionsCategoryInfo categoryInfo) {
return new StatusItem(R.string.ntp_status_card_title_no_suggestions,
categoryInfo.getNoSuggestionDescription(), 0);
}
protected void performAction(Context context) {}
protected boolean hasAction() {
return true;
return mActionStringId != 0;
}
@Override
......
......@@ -4,13 +4,29 @@
package org.chromium.chrome.browser.ntp.cards;
import android.support.annotation.StringRes;
import org.chromium.base.Log;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout.ContentSuggestionsCardLayoutEnum;
import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
/**
* Contains static meta information about a Category. Equivalent of the CategoryInfo class in
* Contains meta information about a Category. Equivalent of the CategoryInfo class in
* components/ntp_snippets/category_info.h.
*/
public class SuggestionsCategoryInfo {
private static final String TAG = "NtpCards";
/**
* Id of the category.
*/
@CategoryInt
private final int mCategory;
/**
* Localized title of the category.
*/
......@@ -32,8 +48,10 @@ public class SuggestionsCategoryInfo {
/** Whether this category should be shown if it offers no suggestions. */
private final boolean mShowIfEmpty;
public SuggestionsCategoryInfo(String title, @ContentSuggestionsCardLayoutEnum int cardLayout,
boolean hasMoreButton, boolean showIfEmpty) {
public SuggestionsCategoryInfo(@CategoryInt int category, String title,
@ContentSuggestionsCardLayoutEnum int cardLayout, boolean hasMoreButton,
boolean showIfEmpty) {
mCategory = category;
mTitle = title;
mCardLayout = cardLayout;
mHasMoreButton = hasMoreButton;
......@@ -44,6 +62,11 @@ public class SuggestionsCategoryInfo {
return mTitle;
}
@CategoryInt
public int getCategory() {
return mCategory;
}
@ContentSuggestionsCardLayoutEnum
public int getCardLayout() {
return mCardLayout;
......@@ -56,4 +79,46 @@ public class SuggestionsCategoryInfo {
public boolean showIfEmpty() {
return mShowIfEmpty;
}
/**
* Performs the appropriate action for the provided category, for the case where there are no
* suggestions available. In general, this consists in navigating to the view showing all the
* content, or fetching new content.
*/
public void performEmptyStateAction(NewTabPageManager manager, NewTabPageAdapter adapter) {
switch (mCategory) {
case KnownCategories.ARTICLES:
adapter.reloadSnippets();
break;
case KnownCategories.BOOKMARKS:
manager.navigateToBookmarks();
break;
case KnownCategories.DOWNLOADS:
manager.navigateToDownloadManager();
break;
case KnownCategories.FOREIGN_TABS:
manager.navigateToRecentTabs();
break;
default:
Log.wtf(TAG, "'Empty State' action called for unsupported category: %d", mCategory);
break;
}
}
/**
* Returns the string to use as description for the status card that is displayed when there
* are no suggestions available for the provided category.
*/
@StringRes
public int getNoSuggestionDescription() {
switch (mCategory) {
case KnownCategories.ARTICLES:
return R.string.ntp_status_card_no_articles;
case KnownCategories.BOOKMARKS:
return R.string.ntp_status_card_no_bookmarks;
default:
Log.wtf(TAG, "Requested description for unsupported category: %d", mCategory);
return 0;
}
}
}
......@@ -5,7 +5,6 @@
package org.chromium.chrome.browser.ntp.cards;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ntp.cards.StatusItem.ActionDelegate;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum;
import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
......@@ -22,39 +21,18 @@ import java.util.List;
public class SuggestionsSection implements ItemGroup {
private final List<SnippetArticle> mSuggestions = new ArrayList<>();
private final SectionHeader mHeader;
private StatusItem mStatus;
private final StatusItem mStatus;
private final ProgressItem mProgressIndicator = new ProgressItem();
private final ActionDelegate mActionDelegate;
private final ActionItem mMoreButton;
@CategoryInt
private final int mCategory;
private final Observer mObserver;
private final SuggestionsCategoryInfo mCategoryInfo;
public SuggestionsSection(@CategoryInt int category, SuggestionsCategoryInfo info,
final NewTabPageAdapter adapter) {
this(category, info, adapter, new ActionDelegate() {
@Override
public void onButtonTapped() {
adapter.reloadSnippets();
}
});
}
@VisibleForTesting
SuggestionsSection(@CategoryInt int category, SuggestionsCategoryInfo info, Observer observer,
ActionDelegate actionDelegate) {
public SuggestionsSection(SuggestionsCategoryInfo info, Observer observer) {
mHeader = new SectionHeader(info.getTitle());
mCategory = category;
mCategoryInfo = info;
mObserver = observer;
// TODO(dgn): Properly define strings, actions, etc. for each section and category type.
if (info.hasMoreButton()) {
mMoreButton = new ActionItem(category);
mActionDelegate = null;
} else {
mMoreButton = null;
mActionDelegate = actionDelegate;
}
mMoreButton = new ActionItem(info);
mStatus = StatusItem.createNoSuggestionsItem(info);
}
@Override
......@@ -65,7 +43,7 @@ public class SuggestionsSection implements ItemGroup {
items.addAll(mSuggestions);
if (mSuggestions.isEmpty()) items.add(mStatus);
if (mMoreButton != null) items.add(mMoreButton);
if (mCategoryInfo.hasMoreButton() || mSuggestions.isEmpty()) items.add(mMoreButton);
if (mSuggestions.isEmpty()) items.add(mProgressIndicator);
return Collections.unmodifiableList(items);
......@@ -82,12 +60,14 @@ public class SuggestionsSection implements ItemGroup {
int globalRemovedIndex = removedIndex + 1; // Header has index 0 in the section.
mObserver.notifyItemRemoved(this, globalRemovedIndex);
if (!hasSuggestions()) {
// When the last suggestion is removed, we insert other items to display the status,
// notify about them too.
mObserver.notifyItemInserted(this, globalRemovedIndex);
mObserver.notifyItemInserted(this, globalRemovedIndex + (mMoreButton == null ? 1 : 2));
// If we still have some suggestions, we are done. Otherwise, we'll have to notify about the
// status-related items that are now present.
if (hasSuggestions()) return;
mObserver.notifyItemInserted(this, globalRemovedIndex); // Status card.
if (!mCategoryInfo.hasMoreButton()) {
mObserver.notifyItemInserted(this, globalRemovedIndex + 1); // Action card.
}
mObserver.notifyItemInserted(this, globalRemovedIndex + 2); // Progress indicator.
}
public void removeSuggestionById(String idWithinCategory) {
......@@ -118,6 +98,7 @@ public class SuggestionsSection implements ItemGroup {
if (mMoreButton != null) {
mMoreButton.setPosition(mSuggestions.size());
mMoreButton.setDismissable(mSuggestions.isEmpty());
}
mObserver.notifyGroupChanged(this, itemCountBefore, getItems().size());
}
......@@ -130,15 +111,14 @@ public class SuggestionsSection implements ItemGroup {
}
private void setStatusInternal(@CategoryStatusEnum int status) {
mStatus = StatusItem.create(status, mActionDelegate);
if (!SnippetsBridge.isCategoryStatusAvailable(status)) mSuggestions.clear();
mProgressIndicator.setVisible(SnippetsBridge.isCategoryLoading(status));
}
@CategoryInt
public int getCategory() {
return mCategory;
return mCategoryInfo.getCategory();
}
private void copyThumbnails(List<SnippetArticle> suggestions) {
......
......@@ -202,9 +202,9 @@ public class SnippetsBridge implements SuggestionsSource {
}
@CalledByNative
private static SuggestionsCategoryInfo createSuggestionsCategoryInfo(
String title, int cardLayout, boolean hasMoreButton, boolean showIfEmpty) {
return new SuggestionsCategoryInfo(title, cardLayout, hasMoreButton, showIfEmpty);
private static SuggestionsCategoryInfo createSuggestionsCategoryInfo(int category, String title,
int cardLayout, boolean hasMoreButton, boolean showIfEmpty) {
return new SuggestionsCategoryInfo(category, title, cardLayout, hasMoreButton, showIfEmpty);
}
@CalledByNative
......
......@@ -173,8 +173,9 @@ public class ArticleSnippetsTest extends ChromeActivityTestCaseBase<ChromeActivi
0, // Position
ContentSuggestionsCardLayout.MINIMAL_CARD);
mSnippetsSource.setInfoForCategory(KnownCategories.ARTICLES, new SuggestionsCategoryInfo(
"Section Title", ContentSuggestionsCardLayout.FULL_CARD, false, true));
mSnippetsSource.setInfoForCategory(KnownCategories.ARTICLES,
new SuggestionsCategoryInfo(KnownCategories.ARTICLES, "Section Title",
ContentSuggestionsCardLayout.FULL_CARD, false, true));
mSnippetsSource.setStatusForCategory(KnownCategories.ARTICLES,
CategoryStatus.AVAILABLE);
mSnippetsSource.setSuggestionsForCategory(KnownCategories.ARTICLES,
......
......@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.ntp.cards;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
......@@ -25,13 +26,15 @@ public final class ContentSuggestionsTestUtils {
return suggestions;
}
public static SuggestionsCategoryInfo createInfo(boolean moreButton, boolean showIfEmpty) {
public static SuggestionsCategoryInfo createInfo(
@CategoryInt int category, boolean moreButton, boolean showIfEmpty) {
return new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.FULL_CARD, moreButton, showIfEmpty);
category, "", ContentSuggestionsCardLayout.FULL_CARD, moreButton, showIfEmpty);
}
public static SuggestionsSection createSection(
boolean moreButton, boolean showIfEmpty, ItemGroup.Observer observer) {
return new SuggestionsSection(42, createInfo(moreButton, showIfEmpty), observer, null);
SuggestionsCategoryInfo info = createInfo(42, moreButton, showIfEmpty);
return new SuggestionsSection(info, observer);
}
}
......@@ -33,6 +33,7 @@ import org.robolectric.annotation.Config;
import static org.chromium.base.test.util.Matchers.greaterThanOrEqualTo;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils
.createDummySuggestions;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createInfo;
import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
......@@ -57,7 +58,6 @@ import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
......@@ -120,9 +120,7 @@ public class NewTabPageAdapterTest {
expect(NewTabPageItem.VIEW_TYPE_HEADER);
if (descriptor.mStatusCard) {
expect(NewTabPageItem.VIEW_TYPE_STATUS);
if (descriptor.mMoreButton) {
expect(NewTabPageItem.VIEW_TYPE_ACTION);
}
expect(NewTabPageItem.VIEW_TYPE_ACTION);
expect(NewTabPageItem.VIEW_TYPE_PROGRESS);
} else {
for (int i = 1; i <= descriptor.mNumSuggestions; i++) {
......@@ -225,11 +223,11 @@ public class NewTabPageAdapterTest {
RecordHistogram.disableForTests();
RecordUserAction.disableForTests();
@CategoryInt
final int category = KnownCategories.ARTICLES;
mSource = new FakeSuggestionsSource();
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.INITIALIZING);
mSource.setInfoForCategory(KnownCategories.ARTICLES,
new SuggestionsCategoryInfo("Articles for you",
ContentSuggestionsCardLayout.FULL_CARD, false, true));
mSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
mSource.setInfoForCategory(category, createInfo(category, false, true));
mAdapter = NewTabPageAdapter.create(new MockNewTabPageManager(mSource), null, null);
}
......@@ -248,36 +246,20 @@ public class NewTabPageAdapterTest {
@Feature({"Ntp"})
public void testSuggestionLoading() {
assertItemsFor(sectionWithStatusCard());
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertEquals(NewTabPageItem.VIEW_TYPE_STATUS, mAdapter.getItemViewType(2));
assertEquals(NewTabPageItem.VIEW_TYPE_PROGRESS, mAdapter.getItemViewType(3));
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(4));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(5));
List<SnippetArticle> suggestions = createDummySuggestions(3);
final int numSuggestions = 3;
List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions);
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(KnownCategories.ARTICLES, suggestions);
int numItems = mAdapter.getItemCount();
// From the loaded items, cut out aboveTheFold and header from the front,
// and footer and bottom spacer from the back.
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertArticlesEqual(suggestions, 2, numItems - 2);
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(numItems - 2));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(numItems - 1));
assertItemsFor(section(numSuggestions));
// The adapter should ignore any new incoming data.
mSource.setSuggestionsForCategory(KnownCategories.ARTICLES,
Arrays.asList(new SnippetArticle[] {new SnippetArticle(0, "foo", "title1", "pub1",
"txt1", "foo", "bar", 0, 0, 0, ContentSuggestionsCardLayout.FULL_CARD)}));
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertArticlesEqual(suggestions, 2, numItems - 2);
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(numItems - 2));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(numItems - 1));
assertItemsFor(section(numSuggestions));
}
/**
......@@ -291,37 +273,20 @@ public class NewTabPageAdapterTest {
mSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, new ArrayList<SnippetArticle>());
assertItemsFor(sectionWithStatusCard());
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertEquals(NewTabPageItem.VIEW_TYPE_STATUS, mAdapter.getItemViewType(2));
assertEquals(NewTabPageItem.VIEW_TYPE_PROGRESS, mAdapter.getItemViewType(3));
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(4));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(5));
// We should load new suggestions when we get notified about them.
List<SnippetArticle> suggestions = createDummySuggestions(5);
final int numSuggestions = 5;
List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions);
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(KnownCategories.ARTICLES, suggestions);
int numItems = mAdapter.getItemCount();
// From the loaded items, cut out aboveTheFold and header from the front,
// and footer and bottom spacer from the back.
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertArticlesEqual(suggestions, 2, numItems - 2);
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(numItems - 2));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(numItems - 1));
assertItemsFor(section(numSuggestions));
// The adapter should ignore any new incoming data.
mSource.setSuggestionsForCategory(KnownCategories.ARTICLES,
Arrays.asList(new SnippetArticle[] {new SnippetArticle(0, "foo", "title1", "pub1",
"txt1", "foo", "bar", 0, 0, 0, ContentSuggestionsCardLayout.FULL_CARD)}));
assertEquals(NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD, mAdapter.getItemViewType(0));
assertEquals(NewTabPageItem.VIEW_TYPE_HEADER, mAdapter.getItemViewType(1));
assertArticlesEqual(suggestions, 2, numItems - 2);
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, mAdapter.getItemViewType(numItems - 2));
assertEquals(NewTabPageItem.VIEW_TYPE_SPACING, mAdapter.getItemViewType(numItems - 1));
assertItemsFor(section(numSuggestions));
}
/**
......@@ -472,6 +437,7 @@ public class NewTabPageAdapterTest {
@Test
@Feature({"Ntp"})
public void testSectionVisibleIfEmpty() {
@CategoryInt
final int category = 42;
final int sectionIdx = 1; // section 0 is the above-the-fold item, we test the one after.
final List<SnippetArticle> articles =
......@@ -481,9 +447,7 @@ public class NewTabPageAdapterTest {
// Part 1: VisibleIfEmpty = true
suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(
category, new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.MINIMAL_CARD, false, true));
suggestionsSource.setInfoForCategory(category, createInfo(category, false, true));
// 1.1 - Initial state
mAdapter =
......@@ -507,9 +471,7 @@ public class NewTabPageAdapterTest {
// Part 2: VisibleIfEmpty = false
suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(
category, new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.MINIMAL_CARD, false, false));
suggestionsSource.setInfoForCategory(category, createInfo(category, false, false));
// 2.1 - Initial state
mAdapter =
......@@ -530,6 +492,7 @@ public class NewTabPageAdapterTest {
@Test
@Feature({"Ntp"})
public void testMoreButton() {
@CategoryInt
final int category = 42;
final int sectionIdx = 1; // section 0 is the above the fold, we test the one after.
final List<SnippetArticle> articles =
......@@ -540,9 +503,7 @@ public class NewTabPageAdapterTest {
// Part 1: ShowMoreButton = true
suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(
category, new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.MINIMAL_CARD, true, true));
suggestionsSource.setInfoForCategory(category, createInfo(category, true, true));
// 1.1 - Initial state.
mAdapter =
......@@ -566,9 +527,7 @@ public class NewTabPageAdapterTest {
// Part 1: ShowMoreButton = false
suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(
category, new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.MINIMAL_CARD, false, true));
suggestionsSource.setInfoForCategory(category, createInfo(category, false, true));
// 2.1 - Initial state.
mAdapter =
......@@ -627,9 +586,7 @@ public class NewTabPageAdapterTest {
int dynamicCategory1 = 1010;
List<SnippetArticle> dynamics1 = createDummySuggestions(5);
mSource.setInfoForCategory(
dynamicCategory1, new SuggestionsCategoryInfo("Dynamic 1",
ContentSuggestionsCardLayout.MINIMAL_CARD, true, false));
mSource.setInfoForCategory(dynamicCategory1, createInfo(dynamicCategory1, true, false));
mSource.setStatusForCategory(dynamicCategory1, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(dynamicCategory1, dynamics1);
mAdapter =
......@@ -638,9 +595,7 @@ public class NewTabPageAdapterTest {
int dynamicCategory2 = 1011;
List<SnippetArticle> dynamics2 = createDummySuggestions(11);
mSource.setInfoForCategory(
dynamicCategory2, new SuggestionsCategoryInfo("Dynamic 2",
ContentSuggestionsCardLayout.MINIMAL_CARD, false, false));
mSource.setInfoForCategory(dynamicCategory2, createInfo(dynamicCategory1, false, false));
mSource.setStatusForCategory(dynamicCategory2, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(dynamicCategory2, dynamics2);
mAdapter =
......@@ -757,6 +712,7 @@ public class NewTabPageAdapterTest {
verify(adapter).notifyItemRemoved(2);
verify(adapter).notifyItemInserted(2);
verify(adapter).notifyItemInserted(3);
verify(adapter).notifyItemInserted(4);
// Adapter content:
// Idx | Item
......@@ -764,16 +720,19 @@ public class NewTabPageAdapterTest {
// 0 | Above-the-fold
// 1 | Header
// 2 | Status
// 3 | Progress Indicator
// 4 | Footer
// 5 | Spacer
// 3 | Action
// 4 | Progress Indicator
// 5 | Footer
// 6 | Spacer
final int newSuggestionCount = 7;
final int changedCount = 3; // status, action and progress will be replaced by articles.
suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(newSuggestionCount));
adapter.onNewSuggestions(KnownCategories.ARTICLES);
verify(adapter).notifyItemRangeChanged(2, 2); // status and progress replaced by articles.
verify(adapter).notifyItemRangeInserted(4, newSuggestionCount - 2);
verify(adapter).notifyItemRangeChanged(2, changedCount);
verify(adapter).notifyItemRangeInserted(
2 + changedCount, newSuggestionCount - changedCount);
// Adapter content:
// Idx | Item
......@@ -787,8 +746,8 @@ public class NewTabPageAdapterTest {
suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(0));
adapter.onCategoryStatusChanged(KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT);
verify(adapter, times(2)).notifyItemRangeChanged(2, 2);
verify(adapter).notifyItemRangeRemoved(4, newSuggestionCount - 2);
verify(adapter, times(2)).notifyItemRangeChanged(2, changedCount);
verify(adapter).notifyItemRangeRemoved(2 + changedCount, newSuggestionCount - changedCount);
}
@Test
......@@ -800,18 +759,19 @@ public class NewTabPageAdapterTest {
NewTabPageAdapter adapter = NewTabPageAdapter.create(ntpManager, null, null);
assertEquals(5, adapter.getGroups().size());
ItemGroup signinPromoGroup = adapter.getGroup(4);
ItemGroup signinPromoGroup = adapter.getGroup(5);
// Adapter content:
// Idx | Item
// ----|----------------
// 0 | Above-the-fold
// 1 | Header
// 2 | Status
// 3 | Progress Indicator
// 4 | Sign in promo
// 5 | Footer
// 6 | Spacer
// Idx | Item | Group Index
// ----|--------------------|-------------
// 0 | Above-the-fold | 0
// 1 | Header | 1
// 2 | Status | 1
// 3 | Action | 1
// 4 | Progress Indicator | 1
// 5 | Sign in promo | 2
// 6 | Footer | 3
// 7 | Spacer | 4
assertEquals(1, signinPromoGroup.getItems().size());
assertEquals(NewTabPageItem.VIEW_TYPE_PROMO, signinPromoGroup.getItems().get(0).getType());
......@@ -832,9 +792,10 @@ public class NewTabPageAdapterTest {
.setNewTabPageSigninPromoDismissed(false);
MockNewTabPageManager ntpManager = new MockNewTabPageManager(mSource);
NewTabPageAdapter adapter = NewTabPageAdapter.create(ntpManager, null, null);
final int signInPromoIndex = 5;
assertEquals(5, adapter.getGroups().size());
ItemGroup signinPromoGroup = adapter.getGroup(4);
ItemGroup signinPromoGroup = adapter.getGroup(signInPromoIndex);
// Adapter content:
// Idx | Item
......@@ -842,14 +803,15 @@ public class NewTabPageAdapterTest {
// 0 | Above-the-fold
// 1 | Header
// 2 | Status
// 3 | Progress Indicator
// 4 | Sign in promo
// 5 | Footer
// 6 | Spacer
// 3 | Action
// 4 | Progress Indicator
// 5 | Sign in promo
// 6 | Footer
// 7 | Spacer
assertEquals(NewTabPageItem.VIEW_TYPE_PROMO, signinPromoGroup.getItems().get(0).getType());
adapter.dismissItem(4);
adapter.dismissItem(signInPromoIndex);
assertTrue(signinPromoGroup.getItems().isEmpty());
assertTrue(ChromePreferenceManager.getInstance(RuntimeEnvironment.application)
.getNewTabPageSigninPromoDismissed());
......@@ -857,7 +819,7 @@ public class NewTabPageAdapterTest {
adapter = NewTabPageAdapter.create(ntpManager, null, null);
assertEquals(5, adapter.getGroups().size());
// The items below the signin promo move up, footer is now at the position of the promo.
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, adapter.getItemViewType(4));
assertEquals(NewTabPageItem.VIEW_TYPE_FOOTER, adapter.getItemViewType(signInPromoIndex));
}
/** Registers the category with hasMoreButton=false and showIfEmpty=true*/
......@@ -867,9 +829,7 @@ public class NewTabPageAdapterTest {
// AVAILABLE.
suggestionsSource.setStatusForCategory(category, CategoryStatus.AVAILABLE);
// Important: showIfEmpty flag to true.
suggestionsSource.setInfoForCategory(
category, new SuggestionsCategoryInfo(
"", ContentSuggestionsCardLayout.FULL_CARD, false, true));
suggestionsSource.setInfoForCategory(category, createInfo(category, false, true));
suggestionsSource.setSuggestionsForCategory(
category, createDummySuggestions(suggestionCount));
}
......@@ -1038,11 +998,7 @@ public class NewTabPageAdapterTest {
@Override
public void closeContextMenu() {
throw new UnsupportedAddressTypeException();
}
public void setSuggestionsSource(SuggestionsSource suggestionsSource) {
mSuggestionsSource = suggestionsSource;
throw new UnsupportedOperationException();
}
@Override
......
......@@ -4,27 +4,28 @@
package org.chromium.chrome.browser.ntp.cards;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createDummySuggestions;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createInfo;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createSection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createDummySuggestions;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createInfo;
import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.createSection;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import java.util.List;
......@@ -34,8 +35,10 @@ import java.util.List;
@RunWith(LocalRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SuggestionsSectionTest {
/** Number of items in a section when there are no suggestions: header, status, progress. */
private static final int EMPTY_SECTION_COUNT = 3;
/**
* Number of items in a section when there are no suggestions: header, status, action, progress.
*/
private static final int EMPTY_SECTION_COUNT = 4;
@Test
@Feature({"Ntp"})
......@@ -44,33 +47,19 @@ public class SuggestionsSectionTest {
List<SnippetArticle> snippets = createDummySuggestions(3);
SuggestionsSection section;
// Part 1: ShowMoreButton = true.
section = new SuggestionsSection(42, createInfo(true, true), observerMock, null);
section = new SuggestionsSection(createInfo(42, true, true), observerMock);
section.setStatus(CategoryStatus.AVAILABLE);
assertNotNull(section.getActionItem());
// 1.1: Without snippets.
// Without snippets.
assertEquals(-1, section.getDismissSiblingPosDelta(section.getActionItem()));
assertEquals(1, section.getDismissSiblingPosDelta(section.getStatusItem()));
// 1.2: With snippets.
// With snippets.
section.setSuggestions(snippets, CategoryStatus.AVAILABLE);
assertEquals(0, section.getDismissSiblingPosDelta(section.getActionItem()));
assertEquals(0, section.getDismissSiblingPosDelta(section.getStatusItem()));
assertEquals(0, section.getDismissSiblingPosDelta(snippets.get(0)));
// Part 2: ShowMoreButton = false.
section = new SuggestionsSection(42, createInfo(false, true), observerMock, null);
section.setStatus(CategoryStatus.AVAILABLE);
assertNull(section.getActionItem());
// 2.1: Without snippets.
assertEquals(0, section.getDismissSiblingPosDelta(section.getStatusItem()));
// 2.2: With snippets.
section.setSuggestions(snippets, CategoryStatus.AVAILABLE);
assertEquals(0, section.getDismissSiblingPosDelta(section.getStatusItem()));
assertEquals(0, section.getDismissSiblingPosDelta(snippets.get(0)));
}
@Test
......@@ -96,7 +85,6 @@ public class SuggestionsSectionTest {
@Feature({"Ntp"})
public void testSetStatusNotification() {
ItemGroup.Observer observerMock = mock(ItemGroup.Observer.class);
final int emptySectionCount = 3;
final int suggestionCount = 5;
List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
......
......@@ -179,7 +179,7 @@ base::android::ScopedJavaLocalRef<jobject> NTPSnippetsBridge::GetCategoryInfo(
if (!info)
return base::android::ScopedJavaLocalRef<jobject>(env, nullptr);
return Java_SnippetsBridge_createSuggestionsCategoryInfo(
env, ConvertUTF16ToJavaString(env, info->title()),
env, category, ConvertUTF16ToJavaString(env, info->title()),
static_cast<int>(info->card_layout()), info->has_more_button(),
info->show_if_empty());
}
......
......@@ -8,15 +8,12 @@
<message name="IDS_SNIPPETS_DISABLED_GENERIC_PROMPT" desc="Title of the card explaining what action the user can take to get content suggestions on the New Tab Page." formatter_data="android_java">
Get suggested content
</message>
<message name="IDS_NTP_STATUS_CARD_TITLE_NO_BOOKMARKS" desc="On the New Tab Page, title of the status card explaining that there is no new content available in the bookmarks section." formatter_data="android_java">
<message name="IDS_NTP_STATUS_CARD_TITLE_NO_SUGGESTIONS" desc="On the New Tab Page, title of the status card explaining that there is no new content available in the section." formatter_data="android_java">
Done for now
</message>
<message name="IDS_NTP_STATUS_CARD_NO_BOOKMARKS" desc="On the New Tab Page, text of the card explaining to the user that they can expect to see bookmarks in this area in the future." formatter_data="android_java">
Your recently visited bookmarks will appear here.
</message>
<message name="IDS_NTP_STATUS_CARD_TITLE_NO_ARTICLES" desc="On the New Tab Page, title of the status card explaining that there is no new content available in the articles section." formatter_data="android_java">
All Read
</message>
<message name="IDS_NTP_STATUS_CARD_NO_ARTICLES" desc="On the New Tab Page, text of the card explaining to the user that they can expect to see suggested articles in this area in the future." formatter_data="android_java">
More articles will appear when the time is right.
</message>
......
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