Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3784)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java

Issue 2513453004: [Android NTP] Move suggestion sections into a separate node. (Closed)
Patch Set: sync Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.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/SectionList.java
similarity index 34%
copy from chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
copy to chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
index aa2fab2040a2760bd113edb1fdf1afa7f78708a6..2143c590c1b5ea768d29bf718f04b28ba84f0b89 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/SectionList.java
@@ -4,28 +4,12 @@
package org.chromium.chrome.browser.ntp.cards;
-import android.annotation.SuppressLint;
-import android.graphics.Canvas;
-import android.support.annotation.StringRes;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Adapter;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.view.View;
-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;
@@ -37,146 +21,35 @@ 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
- * that will be used to back the NTP RecyclerView. The first element in the adapter should always be
- * 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
+ * A node in the tree containing a list of all suggestions sections. It listens to changes in the
+ * suggestions source and updates the corresponding sections.
*/
-public class NewTabPageAdapter
- extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Observer, NodeParent {
+public class SectionList extends InnerNode implements SuggestionsSource.Observer {
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 AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
- 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 final List<TreeNode> mChildren = new ArrayList<>();
+ private final NewTabPageManager mNewTabPageManager;
+ private final OfflinePageBridge mOfflinePageBridge;
- private class ItemTouchCallbacks extends ItemTouchHelper.Callback {
- @Override
- public void onSwiped(ViewHolder viewHolder, int direction) {
- mRecyclerView.onItemDismissStarted(viewHolder);
- NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition());
- }
-
- @Override
- public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
- // clearView() is called when an interaction with the item is finished, which does
- // not mean that the user went all the way and dismissed the item before releasing it.
- // We need to check that the item has been removed.
- if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) {
- mRecyclerView.onItemDismissFinished(viewHolder);
- }
-
- super.clearView(recyclerView, viewHolder);
- }
-
- @Override
- public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
- assert false; // Drag and drop not supported, the method will never be called.
- return false;
- }
-
- @Override
- public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
- assert viewHolder instanceof NewTabPageViewHolder;
-
- int swipeFlags = 0;
- if (((NewTabPageViewHolder) viewHolder).isDismissable()) {
- swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
- }
-
- return makeMovementFlags(0 /* dragFlags */, swipeFlags);
- }
+ public SectionList(NodeParent parent, NewTabPageManager newTabPageManager,
+ OfflinePageBridge offlinePageBridge) {
+ super(parent);
+ mNewTabPageManager = newTabPageManager;
+ mNewTabPageManager.getSuggestionsSource().setObserver(this);
dgn 2016/12/13 14:54:47 setObserver will hook into the native code and mig
+ mOfflinePageBridge = offlinePageBridge;
+ }
- @Override
- public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder,
- float dX, float dY, int actionState, boolean isCurrentlyActive) {
- assert viewHolder instanceof NewTabPageViewHolder;
-
- // The item has already been removed. We have nothing more to do.
- // In some cases a removed children may call this method when unrelated items are
- // interacted with, but this check also covers the case.
- // See https://crbug.com/664466, b/32900699
- if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) return;
-
- // We use our own implementation of the dismissal animation, so we don't call the
- // parent implementation. (by default it changes the translation-X and elevation)
- mRecyclerView.updateViewStateForDismiss(dX, (NewTabPageViewHolder) viewHolder);
-
- // If there is another item that should be animated at the same time, do the same to it.
- NewTabPageViewHolder siblingViewHolder = getDismissSibling(viewHolder);
- if (siblingViewHolder != null) {
- mRecyclerView.updateViewStateForDismiss(dX, siblingViewHolder);
- }
- }
+ @Override
+ public void init() {
+ super.init();
+ resetSections(/* alwaysAllowEmptySections = */ false);
}
- /**
- * Creates the adapter that will manage all the cards to display on the NTP.
- *
- * @param manager the NewTabPageManager to use to interact with the rest of the system.
- * @param aboveTheFoldView the layout encapsulating all the above-the-fold elements
- * (logo, search box, most visited tiles)
- * @param uiConfig the NTP UI configuration, to be passed to created views.
- * @param offlinePageBridge the OfflinePageBridge used to determine if articles are available
- * offline.
- *
- */
- public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, UiConfig uiConfig,
- OfflinePageBridge offlinePageBridge) {
- 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);
- 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);
+ @Override
+ protected List<TreeNode> getChildren() {
+ return mChildren;
}
/**
@@ -204,8 +77,6 @@ public class NewTabPageAdapter
}
mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPerCategory);
-
- updateChildren();
}
/**
@@ -224,17 +95,20 @@ public class NewTabPageAdapter
List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCategory(category);
SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(category);
+ SuggestionsSection section = mSections.get(category);
+
// Do not show an empty section if not allowed.
if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySections) {
- mSections.remove(category);
+ if (section != null) removeSection(section);
return 0;
}
// Create the section if needed.
- SuggestionsSection section = mSections.get(category);
if (section == null) {
- section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mOfflinePageBridge);
+ section = new SuggestionsSection(this, mNewTabPageManager, mOfflinePageBridge, info);
mSections.put(category, section);
+ mChildren.add(section);
+ didAddChild(section);
}
// Add the new suggestions.
@@ -243,11 +117,6 @@ public class NewTabPageAdapter
return suggestions.size();
}
- /** Returns callbacks to configure the interactions with the RecyclerView's items. */
- public ItemTouchHelper.Callback getItemTouchCallbacks() {
- return mItemTouchCallbacks;
- }
-
@Override
public void onNewSuggestions(@CategoryInt int category) {
@CategoryStatusEnum
@@ -298,7 +167,9 @@ public class NewTabPageAdapter
return;
case CategoryStatus.SIGNED_OUT:
- // TODO(dgn): We currently can only reach this through an old variation parameter.
+ resetSection(category, status, /* alwaysAllowEmptySections = */ false);
+ return;
+
default:
mSections.get(category).setStatus(status);
return;
@@ -313,86 +184,7 @@ public class NewTabPageAdapter
@Override
public void onFullRefreshRequired() {
- resetSections(/*alwaysAllowEmptySections=*/false);
- }
-
- @Override
- @ItemViewType
- public int getItemViewType(int position) {
- return mRoot.getItemViewType(position);
- }
-
- @Override
- public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- assert parent == mRecyclerView;
-
- switch (viewType) {
- case ItemViewType.ABOVE_THE_FOLD:
- return new NewTabPageViewHolder(mAboveTheFoldView);
-
- case ItemViewType.HEADER:
- return new SectionHeaderViewHolder(mRecyclerView, mUiConfig);
-
- case ItemViewType.SNIPPET:
- return new SnippetArticleViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig);
-
- case ItemViewType.SPACING:
- return new NewTabPageViewHolder(SpacingItem.createView(parent));
-
- case ItemViewType.STATUS:
- return new StatusCardViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig);
-
- case ItemViewType.PROGRESS:
- return new ProgressViewHolder(mRecyclerView);
-
- case ItemViewType.ACTION:
- return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig);
-
- case ItemViewType.PROMO:
- return new SignInPromo.ViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig);
-
- case ItemViewType.FOOTER:
- return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
-
- case ItemViewType.ALL_DISMISSED:
- return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPageManager, this);
- }
-
- assert false : viewType;
- return null;
- }
-
- @Override
- public void onBindViewHolder(NewTabPageViewHolder holder, final int position) {
- mRoot.onBindViewHolder(holder, position);
- }
-
- @Override
- public int getItemCount() {
- return mRoot.getItemCount();
- }
-
- public int getAboveTheFoldPosition() {
- return getChildPositionOffset(mAboveTheFold);
- }
-
- public int getFirstHeaderPosition() {
- return getFirstPositionForType(ItemViewType.HEADER);
- }
-
- public int getFirstCardPosition() {
- for (int i = 0; i < getItemCount(); ++i) {
- if (CardViewHolder.isCard(getItemViewType(i))) return i;
- }
- return RecyclerView.NO_POSITION;
- }
-
- int getBottomSpacerPosition() {
- return getChildPositionOffset(mBottomSpacer);
- }
-
- int getLastContentItemPosition() {
- return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter);
+ resetSections(/* alwaysAllowEmptySections = */false);
}
private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
@@ -411,155 +203,6 @@ public class NewTabPageAdapter
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();
- }
-
- private void updateAllDismissedVisibility() {
- boolean showAllDismissed = hasAllBeenDismissed();
- mAllDismissed.setVisible(showAllDismissed);
- 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;
- notifyItemRangeChanged(itemPosition, itemCount);
- }
-
- @Override
- public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCount) {
- assert child == mRoot;
- notifyItemRangeInserted(itemPosition, itemCount);
- notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
-
- updateAllDismissedVisibility();
- }
-
- @Override
- public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCount) {
- assert child == mRoot;
- notifyItemRangeRemoved(itemPosition, itemCount);
- notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
-
- updateAllDismissedVisibility();
- }
-
- @Override
- public void onAttachedToRecyclerView(RecyclerView recyclerView) {
- super.onAttachedToRecyclerView(recyclerView);
-
- // We are assuming for now that the adapter is used with a single RecyclerView.
- // Getting the reference as we are doing here is going to be broken if that changes.
- assert mRecyclerView == null;
-
- // FindBugs chokes on the cast below when not checked, raising BC_UNCONFIRMED_CAST
- assert recyclerView instanceof NewTabPageRecyclerView;
-
- mRecyclerView = (NewTabPageRecyclerView) recyclerView;
- }
-
- /**
- * Dismisses the item at the provided adapter position. Can also cause the dismissal of other
- * items or even entire sections.
- */
- // TODO(crbug.com/635567): Fix this properly.
- @SuppressLint("SwitchIntDef")
- public void dismissItem(int position) {
- int itemViewType = getItemViewType(position);
-
- // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of stuff.
- switch (itemViewType) {
- case ItemViewType.STATUS:
- case ItemViewType.ACTION:
- dismissSection(getSuggestionsSection(position));
- return;
-
- case ItemViewType.SNIPPET:
- dismissSuggestion(position);
- return;
-
- case ItemViewType.PROMO:
- dismissPromo();
- return;
-
- default:
- Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemViewType);
- return;
- }
- }
-
- private void dismissSection(SuggestionsSection section) {
- assert SnippetsConfig.isSectionDismissalEnabled();
-
- announceItemRemoved(section.getHeaderText());
-
- mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory());
- removeSection(section);
- }
-
- private void dismissSuggestion(int position) {
- SnippetArticle suggestion = mRoot.getSuggestionAt(position);
- SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
- if (suggestionsSource == null) {
- // It is possible for this method to be called after the NewTabPage has had destroy()
- // called. This can happen when NewTabPageRecyclerView.dismissWithAnimation() is called
- // and the animation ends after the user has navigated away. In this case we cannot
- // inform the native side that the snippet has been dismissed (http://crbug.com/649299).
- return;
- }
-
- announceItemRemoved(suggestion.mTitle);
-
- suggestionsSource.dismissSuggestion(suggestion);
- SuggestionsSection section = getSuggestionsSection(position);
- section.removeSuggestion(suggestion);
- }
-
- private void dismissPromo() {
- announceItemRemoved(mSigninPromo.getHeader());
- mSigninPromo.dismiss();
- }
-
- /**
- * Returns another view holder that should be dismissed at the same time as the provided one.
- */
- public NewTabPageViewHolder getDismissSibling(ViewHolder viewHolder) {
- int swipePos = viewHolder.getAdapterPosition();
- int siblingPosDelta = mRoot.getDismissSiblingPosDelta(swipePos);
- if (siblingPosDelta == 0) return null;
-
- return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosition(
- siblingPosDelta + swipePos);
- }
-
- private boolean hasAllBeenDismissed() {
- 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;
@@ -576,54 +219,39 @@ public class NewTabPageAdapter
}
/**
- * @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.
+ * Dismisses a section.
+ * @param section The section to be dismissed.
*/
- private SuggestionsSection getSuggestionsSection(int itemPosition) {
- TreeNode child = mRoot.getChildForPosition(itemPosition);
- if (!(child instanceof SuggestionsSection)) return null;
- return (SuggestionsSection) child;
- }
-
- private int getChildPositionOffset(TreeNode child) {
- return mRoot.getStartingOffsetForChild(child);
- }
-
- @VisibleForTesting
- SnippetArticle getSuggestionAt(int position) {
- return mRoot.getSuggestionAt(position);
- }
+ public void dismissSection(SuggestionsSection section) {
+ assert SnippetsConfig.isSectionDismissalEnabled();
- @VisibleForTesting
- int getFirstPositionForType(@ItemViewType int viewType) {
- int count = getItemCount();
- for (int i = 0; i < count; i++) {
- if (getItemViewType(i) == viewType) return i;
- }
- return RecyclerView.NO_POSITION;
+ mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory());
+ removeSection(section);
}
- SuggestionsSection getSectionForTesting(@CategoryInt int category) {
- return mSections.get(category);
+ private void removeSection(SuggestionsSection section) {
+ mSections.remove(section.getCategory());
+ willRemoveChild(section);
+ mChildren.remove(section);
}
- InnerNode getRootForTesting() {
- return mRoot;
+ /**
+ * Restores any sections that have been dismissed and triggers a new fetch.
+ */
+ public void restoreDismissedSections() {
+ mNewTabPageManager.getSuggestionsSource().restoreDismissedCategories();
+ resetSections(/* allowEmptySections = */ true);
+ mNewTabPageManager.getSuggestionsSource().fetchRemoteSuggestions();
}
- private void announceItemRemoved(String itemTitle) {
- // In tests the RecyclerView can be null.
- if (mRecyclerView == null) return;
-
- mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getString(
- R.string.ntp_accessibility_item_removed, itemTitle));
+ /**
+ * @return Whether the list of sections is empty.
+ */
+ public boolean isEmpty() {
+ return mSections.isEmpty();
}
- private void announceItemRemoved(@StringRes int stringToAnnounce) {
- // In tests the RecyclerView can be null.
- if (mRecyclerView == null) return;
-
- announceItemRemoved(mRecyclerView.getResources().getString(stringToAnnounce));
+ SuggestionsSection getSectionForTesting(@CategoryInt int categoryId) {
+ return mSections.get(categoryId);
}
}

Powered by Google App Engine
This is Rietveld 408576698