| Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
|
| index aa2fab2040a2760bd113edb1fdf1afa7f78708a6..ebb7b2d15b1f9eaa2fb4843c2fd1e18f55548788 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
|
| @@ -17,24 +17,16 @@ import android.view.ViewGroup;
|
| import org.chromium.base.Log;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.chrome.R;
|
| -import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
|
| import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
|
| import org.chromium.chrome.browser.ntp.UiConfig;
|
| -import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
|
| -import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
|
| -import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum;
|
| import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
|
| import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
|
| import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
|
| -import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
|
| -import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig;
|
| import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
|
| import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
|
|
|
| -import java.util.ArrayList;
|
| -import java.util.LinkedHashMap;
|
| +import java.util.Arrays;
|
| import java.util.List;
|
| -import java.util.Map;
|
|
|
| /**
|
| * A class that handles merging above the fold elements and below the fold cards into an adapter
|
| @@ -42,30 +34,27 @@ import java.util.Map;
|
| * the above-the-fold view (containing the logo, search box, and most visited tiles) and subsequent
|
| * elements will be the cards shown to the user
|
| */
|
| -public class NewTabPageAdapter
|
| - extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Observer, NodeParent {
|
| +public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements NodeParent {
|
| private static final String TAG = "Ntp";
|
|
|
| private final NewTabPageManager mNewTabPageManager;
|
| private final View mAboveTheFoldView;
|
| private final UiConfig mUiConfig;
|
| private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallbacks();
|
| - private final OfflinePageBridge mOfflinePageBridge;
|
| private NewTabPageRecyclerView mRecyclerView;
|
|
|
| /**
|
| * List of all child nodes (which can themselves contain multiple child nodes).
|
| */
|
| - private final List<TreeNode> mChildren = new ArrayList<>();
|
| + private final List<TreeNode> mChildren;
|
| + private final InnerNode mRoot;
|
| +
|
| private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
|
| + private final SectionList mSections;
|
| private final SignInPromo mSigninPromo;
|
| private final AllDismissedItem mAllDismissed;
|
| private final Footer mFooter;
|
| private final SpacingItem mBottomSpacer = new SpacingItem();
|
| - private final InnerNode mRoot;
|
| -
|
| - /** Maps suggestion categories to sections, with stable iteration ordering. */
|
| - private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap<>();
|
|
|
| private class ItemTouchCallbacks extends ItemTouchHelper.Callback {
|
| @Override
|
| @@ -143,104 +132,23 @@ public class NewTabPageAdapter
|
| mNewTabPageManager = manager;
|
| mAboveTheFoldView = aboveTheFoldView;
|
| mUiConfig = uiConfig;
|
| - mOfflinePageBridge = offlinePageBridge;
|
| mRoot = new InnerNode(this) {
|
| @Override
|
| protected List<TreeNode> getChildren() {
|
| return mChildren;
|
| }
|
| -
|
| - @Override
|
| - public void onItemRangeChanged(TreeNode child, int index, int count) {
|
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet.
|
| - super.onItemRangeChanged(child, index, count);
|
| - }
|
| -
|
| - @Override
|
| - public void onItemRangeInserted(TreeNode child, int index, int count) {
|
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet.
|
| - super.onItemRangeInserted(child, index, count);
|
| - }
|
| -
|
| - @Override
|
| - public void onItemRangeRemoved(TreeNode child, int index, int count) {
|
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet.
|
| - super.onItemRangeRemoved(child, index, count);
|
| - }
|
| };
|
|
|
| - mSigninPromo = new SignInPromo(mRoot);
|
| + mSections = new SectionList(mRoot, mNewTabPageManager, offlinePageBridge);
|
| + mSigninPromo = new SignInPromo(mRoot, mNewTabPageManager);
|
| mAllDismissed = new AllDismissedItem(mRoot);
|
| mFooter = new Footer(mRoot);
|
| - DestructionObserver signInObserver = mSigninPromo.getObserver();
|
| - if (signInObserver != null) mNewTabPageManager.addDestructionObserver(signInObserver);
|
| -
|
| - resetSections(/*alwaysAllowEmptySections=*/false);
|
| - mNewTabPageManager.getSuggestionsSource().setObserver(this);
|
| - }
|
| -
|
| - /**
|
| - * Resets the sections, reloading the whole new tab page content.
|
| - * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
|
| - * they are empty, even when they are normally not.
|
| - */
|
| - public void resetSections(boolean alwaysAllowEmptySections) {
|
| - mSections.clear();
|
| - mChildren.clear();
|
| -
|
| - SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
|
| - int[] categories = suggestionsSource.getCategories();
|
| - int[] suggestionsPerCategory = new int[categories.length];
|
| - int i = 0;
|
| - for (int category : categories) {
|
| - int categoryStatus = suggestionsSource.getCategoryStatus(category);
|
| - if (categoryStatus == CategoryStatus.LOADING_ERROR
|
| - || categoryStatus == CategoryStatus.NOT_PROVIDED
|
| - || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISABLED)
|
| - continue;
|
| -
|
| - suggestionsPerCategory[i++] =
|
| - resetSection(category, categoryStatus, alwaysAllowEmptySections);
|
| - }
|
| -
|
| - mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPerCategory);
|
| -
|
| - updateChildren();
|
| - }
|
| -
|
| - /**
|
| - * Resets the section for {@code category}. Removes the section if there are no suggestions for
|
| - * it and it is not allowed to be empty. Otherwise, creates the section if it is not present
|
| - * yet. Sets the available suggestions on the section.
|
| - * @param category The category for which the section must be reset.
|
| - * @param categoryStatus The category status.
|
| - * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
|
| - * they are empty, even when they are normally not.
|
| - * @return The number of suggestions for the section.
|
| - */
|
| - private int resetSection(@CategoryInt int category, @CategoryStatusEnum int categoryStatus,
|
| - boolean alwaysAllowEmptySections) {
|
| - SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
|
| - List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCategory(category);
|
| - SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(category);
|
| -
|
| - // Do not show an empty section if not allowed.
|
| - if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySections) {
|
| - mSections.remove(category);
|
| - return 0;
|
| - }
|
| -
|
| - // Create the section if needed.
|
| - SuggestionsSection section = mSections.get(category);
|
| - if (section == null) {
|
| - section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mOfflinePageBridge);
|
| - mSections.put(category, section);
|
| - }
|
|
|
| - // Add the new suggestions.
|
| - setSuggestions(category, suggestions, categoryStatus);
|
| + mChildren = Arrays.asList(
|
| + mAboveTheFold, mSections, mSigninPromo, mAllDismissed, mFooter, mBottomSpacer);
|
| + mRoot.init();
|
|
|
| - return suggestions.size();
|
| + updateAllDismissedVisibility();
|
| }
|
|
|
| /** Returns callbacks to configure the interactions with the RecyclerView's items. */
|
| @@ -249,74 +157,6 @@ public class NewTabPageAdapter
|
| }
|
|
|
| @Override
|
| - public void onNewSuggestions(@CategoryInt int category) {
|
| - @CategoryStatusEnum
|
| - int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
|
| -
|
| - if (!canLoadSuggestions(category, status)) return;
|
| -
|
| - // We never want to refresh the suggestions if we already have some content.
|
| - if (mSections.get(category).hasSuggestions()) return;
|
| -
|
| - List<SnippetArticle> suggestions =
|
| - mNewTabPageManager.getSuggestionsSource().getSuggestionsForCategory(category);
|
| -
|
| - Log.d(TAG, "Received %d new suggestions for category %d.", suggestions.size(), category);
|
| -
|
| - // At first, there might be no suggestions available, we wait until they have been fetched.
|
| - if (suggestions.isEmpty()) return;
|
| -
|
| - setSuggestions(category, suggestions, status);
|
| - }
|
| -
|
| - @Override
|
| - public void onMoreSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions) {
|
| - @CategoryStatusEnum
|
| - int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
|
| - if (!canLoadSuggestions(category, status)) return;
|
| -
|
| - setSuggestions(category, suggestions, status);
|
| - }
|
| -
|
| - @Override
|
| - public void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatusEnum int status) {
|
| - // Observers should not be registered for this state.
|
| - assert status != CategoryStatus.ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
|
| -
|
| - // If there is no section for this category there is nothing to do.
|
| - if (!mSections.containsKey(category)) return;
|
| -
|
| - switch (status) {
|
| - case CategoryStatus.NOT_PROVIDED:
|
| - // The section provider has gone away. Keep open UIs as they are.
|
| - return;
|
| -
|
| - case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
|
| - case CategoryStatus.LOADING_ERROR:
|
| - // Need to remove the entire section from the UI immediately.
|
| - removeSection(mSections.get(category));
|
| - return;
|
| -
|
| - case CategoryStatus.SIGNED_OUT:
|
| - // TODO(dgn): We currently can only reach this through an old variation parameter.
|
| - default:
|
| - mSections.get(category).setStatus(status);
|
| - return;
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {
|
| - if (!mSections.containsKey(category)) return;
|
| - mSections.get(category).removeSuggestionById(idWithinCategory);
|
| - }
|
| -
|
| - @Override
|
| - public void onFullRefreshRequired() {
|
| - resetSections(/*alwaysAllowEmptySections=*/false);
|
| - }
|
| -
|
| - @Override
|
| @ItemViewType
|
| public int getItemViewType(int position) {
|
| return mRoot.getItemViewType(position);
|
| @@ -355,7 +195,7 @@ public class NewTabPageAdapter
|
| return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
|
|
|
| case ItemViewType.ALL_DISMISSED:
|
| - return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPageManager, this);
|
| + return new AllDismissedItem.ViewHolder(mRecyclerView, mSections);
|
| }
|
|
|
| assert false : viewType;
|
| @@ -387,44 +227,12 @@ public class NewTabPageAdapter
|
| return RecyclerView.NO_POSITION;
|
| }
|
|
|
| - int getBottomSpacerPosition() {
|
| - return getChildPositionOffset(mBottomSpacer);
|
| - }
|
| -
|
| int getLastContentItemPosition() {
|
| return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter);
|
| }
|
|
|
| - private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
|
| - @CategoryStatusEnum int status) {
|
| - // Count the number of suggestions before this category.
|
| - int globalPositionOffset = 0;
|
| - for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()) {
|
| - if (entry.getKey() == category) break;
|
| - globalPositionOffset += entry.getValue().getSuggestionsCount();
|
| - }
|
| - // Assign global indices to the new suggestions.
|
| - for (SnippetArticle suggestion : suggestions) {
|
| - suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosition;
|
| - }
|
| -
|
| - mSections.get(category).addSuggestions(suggestions, status);
|
| - }
|
| -
|
| - private void updateChildren() {
|
| - mChildren.clear();
|
| - mChildren.add(mAboveTheFold);
|
| - mChildren.addAll(mSections.values());
|
| - mChildren.add(mSigninPromo);
|
| - mChildren.add(mAllDismissed);
|
| - mChildren.add(mFooter);
|
| - mChildren.add(mBottomSpacer);
|
| -
|
| - updateAllDismissedVisibility();
|
| -
|
| - // TODO(mvanouwerkerk): Notify about the subset of changed items. At least |mAboveTheFold|
|
| - // has not changed when refreshing from the all dismissed state.
|
| - notifyDataSetChanged();
|
| + int getBottomSpacerPosition() {
|
| + return getChildPositionOffset(mBottomSpacer);
|
| }
|
|
|
| private void updateAllDismissedVisibility() {
|
| @@ -433,17 +241,6 @@ public class NewTabPageAdapter
|
| mFooter.setVisible(!showAllDismissed);
|
| }
|
|
|
| - private void removeSection(SuggestionsSection section) {
|
| - mSections.remove(section.getCategory());
|
| - int startPos = getChildPositionOffset(section);
|
| - mChildren.remove(section);
|
| - notifyItemRangeRemoved(startPos, section.getItemCount());
|
| -
|
| - updateAllDismissedVisibility();
|
| -
|
| - notifyItemChanged(getBottomSpacerPosition());
|
| - }
|
| -
|
| @Override
|
| public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCount) {
|
| assert child == mRoot;
|
| @@ -495,7 +292,7 @@ public class NewTabPageAdapter
|
| switch (itemViewType) {
|
| case ItemViewType.STATUS:
|
| case ItemViewType.ACTION:
|
| - dismissSection(getSuggestionsSection(position));
|
| + dismissSection(position);
|
| return;
|
|
|
| case ItemViewType.SNIPPET:
|
| @@ -512,13 +309,10 @@ public class NewTabPageAdapter
|
| }
|
| }
|
|
|
| - private void dismissSection(SuggestionsSection section) {
|
| - assert SnippetsConfig.isSectionDismissalEnabled();
|
| -
|
| + private void dismissSection(int position) {
|
| + SuggestionsSection section = getSuggestionsSection(position);
|
| + mSections.dismissSection(section);
|
| announceItemRemoved(section.getHeaderText());
|
| -
|
| - mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory());
|
| - removeSection(section);
|
| }
|
|
|
| private void dismissSuggestion(int position) {
|
| @@ -560,28 +354,15 @@ public class NewTabPageAdapter
|
| return mSections.isEmpty() && !mSigninPromo.isVisible();
|
| }
|
|
|
| - private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatusEnum int status) {
|
| - // We never want to add suggestions from unknown categories.
|
| - if (!mSections.containsKey(category)) return false;
|
| -
|
| - // The status may have changed while the suggestions were loading, perhaps they should not
|
| - // be displayed any more.
|
| - if (!SnippetsBridge.isCategoryEnabled(status)) {
|
| - Log.w(TAG, "Received suggestions for a disabled category (id=%d, status=%d)", category,
|
| - status);
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| - }
|
| -
|
| /**
|
| * @param itemPosition The position of an item in the adapter.
|
| * @return Returns the {@link SuggestionsSection} that contains the item at
|
| * {@code itemPosition}, or null if the item is not part of one.
|
| */
|
| private SuggestionsSection getSuggestionsSection(int itemPosition) {
|
| - TreeNode child = mRoot.getChildForPosition(itemPosition);
|
| + int relativePosition = itemPosition - mRoot.getStartingOffsetForChild(mSections);
|
| + assert relativePosition >= 0;
|
| + TreeNode child = mSections.getChildForPosition(relativePosition);
|
| if (!(child instanceof SuggestionsSection)) return null;
|
| return (SuggestionsSection) child;
|
| }
|
| @@ -604,8 +385,8 @@ public class NewTabPageAdapter
|
| return RecyclerView.NO_POSITION;
|
| }
|
|
|
| - SuggestionsSection getSectionForTesting(@CategoryInt int category) {
|
| - return mSections.get(category);
|
| + SectionList getSectionListForTesting() {
|
| + return mSections;
|
| }
|
|
|
| InnerNode getRootForTesting() {
|
|
|