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);
}
}
......@@ -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