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

Side by Side 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 unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.ntp.cards; 5 package org.chromium.chrome.browser.ntp.cards;
6 6
7 import android.annotation.SuppressLint;
8 import android.graphics.Canvas;
9 import android.support.annotation.StringRes;
10 import android.support.v7.widget.RecyclerView;
11 import android.support.v7.widget.RecyclerView.Adapter;
12 import android.support.v7.widget.RecyclerView.ViewHolder;
13 import android.support.v7.widget.helper.ItemTouchHelper;
14 import android.view.View;
15 import android.view.ViewGroup;
16
17 import org.chromium.base.Log; 7 import org.chromium.base.Log;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.chrome.R;
20 import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
21 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; 8 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
22 import org.chromium.chrome.browser.ntp.UiConfig;
23 import org.chromium.chrome.browser.ntp.snippets.CategoryInt; 9 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
24 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; 10 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
25 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu m; 11 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu m;
26 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
27 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; 12 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
28 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
29 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; 13 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
30 import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig; 14 import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig;
31 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; 15 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
32 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; 16 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
33 17
34 import java.util.ArrayList; 18 import java.util.ArrayList;
35 import java.util.LinkedHashMap; 19 import java.util.LinkedHashMap;
36 import java.util.List; 20 import java.util.List;
37 import java.util.Map; 21 import java.util.Map;
38 22
39 /** 23 /**
40 * A class that handles merging above the fold elements and below the fold cards into an adapter 24 * A node in the tree containing a list of all suggestions sections. It listens to changes in the
41 * that will be used to back the NTP RecyclerView. The first element in the adap ter should always be 25 * suggestions source and updates the corresponding sections.
42 * the above-the-fold view (containing the logo, search box, and most visited ti les) and subsequent
43 * elements will be the cards shown to the user
44 */ 26 */
45 public class NewTabPageAdapter 27 public class SectionList extends InnerNode implements SuggestionsSource.Observer {
46 extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Obser ver, NodeParent {
47 private static final String TAG = "Ntp"; 28 private static final String TAG = "Ntp";
48 29
49 private final NewTabPageManager mNewTabPageManager;
50 private final View mAboveTheFoldView;
51 private final UiConfig mUiConfig;
52 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback s();
53 private final OfflinePageBridge mOfflinePageBridge;
54 private NewTabPageRecyclerView mRecyclerView;
55
56 /**
57 * List of all child nodes (which can themselves contain multiple child node s).
58 */
59 private final List<TreeNode> mChildren = new ArrayList<>();
60 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
61 private final SignInPromo mSigninPromo;
62 private final AllDismissedItem mAllDismissed;
63 private final Footer mFooter;
64 private final SpacingItem mBottomSpacer = new SpacingItem();
65 private final InnerNode mRoot;
66
67 /** Maps suggestion categories to sections, with stable iteration ordering. */ 30 /** Maps suggestion categories to sections, with stable iteration ordering. */
68 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap <>(); 31 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap <>();
32 private final List<TreeNode> mChildren = new ArrayList<>();
33 private final NewTabPageManager mNewTabPageManager;
34 private final OfflinePageBridge mOfflinePageBridge;
69 35
70 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { 36 public SectionList(NodeParent parent, NewTabPageManager newTabPageManager,
71 @Override 37 OfflinePageBridge offlinePageBridge) {
72 public void onSwiped(ViewHolder viewHolder, int direction) { 38 super(parent);
73 mRecyclerView.onItemDismissStarted(viewHolder); 39 mNewTabPageManager = newTabPageManager;
74 NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition()); 40 mNewTabPageManager.getSuggestionsSource().setObserver(this);
dgn 2016/12/13 14:54:47 setObserver will hook into the native code and mig
75 } 41 mOfflinePageBridge = offlinePageBridge;
42 }
76 43
77 @Override 44 @Override
78 public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) { 45 public void init() {
79 // clearView() is called when an interaction with the item is finish ed, which does 46 super.init();
80 // not mean that the user went all the way and dismissed the item be fore releasing it. 47 resetSections(/* alwaysAllowEmptySections = */ false);
81 // We need to check that the item has been removed. 48 }
82 if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) {
83 mRecyclerView.onItemDismissFinished(viewHolder);
84 }
85 49
86 super.clearView(recyclerView, viewHolder); 50 @Override
87 } 51 protected List<TreeNode> getChildren() {
88 52 return mChildren;
89 @Override
90 public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
91 assert false; // Drag and drop not supported, the method will never be called.
92 return false;
93 }
94
95 @Override
96 public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHo lder) {
97 assert viewHolder instanceof NewTabPageViewHolder;
98
99 int swipeFlags = 0;
100 if (((NewTabPageViewHolder) viewHolder).isDismissable()) {
101 swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
102 }
103
104 return makeMovementFlags(0 /* dragFlags */, swipeFlags);
105 }
106
107 @Override
108 public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder,
109 float dX, float dY, int actionState, boolean isCurrentlyActive) {
110 assert viewHolder instanceof NewTabPageViewHolder;
111
112 // The item has already been removed. We have nothing more to do.
113 // In some cases a removed children may call this method when unrela ted items are
114 // interacted with, but this check also covers the case.
115 // See https://crbug.com/664466, b/32900699
116 if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) ret urn;
117
118 // We use our own implementation of the dismissal animation, so we d on't call the
119 // parent implementation. (by default it changes the translation-X a nd elevation)
120 mRecyclerView.updateViewStateForDismiss(dX, (NewTabPageViewHolder) v iewHolder);
121
122 // If there is another item that should be animated at the same time , do the same to it.
123 NewTabPageViewHolder siblingViewHolder = getDismissSibling(viewHolde r);
124 if (siblingViewHolder != null) {
125 mRecyclerView.updateViewStateForDismiss(dX, siblingViewHolder);
126 }
127 }
128 } 53 }
129 54
130 /** 55 /**
131 * Creates the adapter that will manage all the cards to display on the NTP.
132 *
133 * @param manager the NewTabPageManager to use to interact with the rest of the system.
134 * @param aboveTheFoldView the layout encapsulating all the above-the-fold e lements
135 * (logo, search box, most visited tiles)
136 * @param uiConfig the NTP UI configuration, to be passed to created views.
137 * @param offlinePageBridge the OfflinePageBridge used to determine if artic les are available
138 * offline.
139 *
140 */
141 public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, U iConfig uiConfig,
142 OfflinePageBridge offlinePageBridge) {
143 mNewTabPageManager = manager;
144 mAboveTheFoldView = aboveTheFoldView;
145 mUiConfig = uiConfig;
146 mOfflinePageBridge = offlinePageBridge;
147 mRoot = new InnerNode(this) {
148 @Override
149 protected List<TreeNode> getChildren() {
150 return mChildren;
151 }
152
153 @Override
154 public void onItemRangeChanged(TreeNode child, int index, int count) {
155 if (mChildren.isEmpty()) return; // The sections have not been i nitialised yet.
156 super.onItemRangeChanged(child, index, count);
157 }
158
159 @Override
160 public void onItemRangeInserted(TreeNode child, int index, int count ) {
161 if (mChildren.isEmpty()) return; // The sections have not been i nitialised yet.
162 super.onItemRangeInserted(child, index, count);
163 }
164
165 @Override
166 public void onItemRangeRemoved(TreeNode child, int index, int count) {
167 if (mChildren.isEmpty()) return; // The sections have not been i nitialised yet.
168 super.onItemRangeRemoved(child, index, count);
169 }
170 };
171
172 mSigninPromo = new SignInPromo(mRoot);
173 mAllDismissed = new AllDismissedItem(mRoot);
174 mFooter = new Footer(mRoot);
175 DestructionObserver signInObserver = mSigninPromo.getObserver();
176 if (signInObserver != null) mNewTabPageManager.addDestructionObserver(si gnInObserver);
177
178 resetSections(/*alwaysAllowEmptySections=*/false);
179 mNewTabPageManager.getSuggestionsSource().setObserver(this);
180 }
181
182 /**
183 * Resets the sections, reloading the whole new tab page content. 56 * Resets the sections, reloading the whole new tab page content.
184 * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when 57 * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
185 * they are empty, even when they are normally not. 58 * they are empty, even when they are normally not.
186 */ 59 */
187 public void resetSections(boolean alwaysAllowEmptySections) { 60 public void resetSections(boolean alwaysAllowEmptySections) {
188 mSections.clear(); 61 mSections.clear();
189 mChildren.clear(); 62 mChildren.clear();
190 63
191 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource(); 64 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource();
192 int[] categories = suggestionsSource.getCategories(); 65 int[] categories = suggestionsSource.getCategories();
193 int[] suggestionsPerCategory = new int[categories.length]; 66 int[] suggestionsPerCategory = new int[categories.length];
194 int i = 0; 67 int i = 0;
195 for (int category : categories) { 68 for (int category : categories) {
196 int categoryStatus = suggestionsSource.getCategoryStatus(category); 69 int categoryStatus = suggestionsSource.getCategoryStatus(category);
197 if (categoryStatus == CategoryStatus.LOADING_ERROR 70 if (categoryStatus == CategoryStatus.LOADING_ERROR
198 || categoryStatus == CategoryStatus.NOT_PROVIDED 71 || categoryStatus == CategoryStatus.NOT_PROVIDED
199 || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISA BLED) 72 || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISA BLED)
200 continue; 73 continue;
201 74
202 suggestionsPerCategory[i++] = 75 suggestionsPerCategory[i++] =
203 resetSection(category, categoryStatus, alwaysAllowEmptySecti ons); 76 resetSection(category, categoryStatus, alwaysAllowEmptySecti ons);
204 } 77 }
205 78
206 mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPe rCategory); 79 mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPe rCategory);
207
208 updateChildren();
209 } 80 }
210 81
211 /** 82 /**
212 * Resets the section for {@code category}. Removes the section if there are no suggestions for 83 * Resets the section for {@code category}. Removes the section if there are no suggestions for
213 * it and it is not allowed to be empty. Otherwise, creates the section if i t is not present 84 * it and it is not allowed to be empty. Otherwise, creates the section if i t is not present
214 * yet. Sets the available suggestions on the section. 85 * yet. Sets the available suggestions on the section.
215 * @param category The category for which the section must be reset. 86 * @param category The category for which the section must be reset.
216 * @param categoryStatus The category status. 87 * @param categoryStatus The category status.
217 * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when 88 * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
218 * they are empty, even when they are normally not. 89 * they are empty, even when they are normally not.
219 * @return The number of suggestions for the section. 90 * @return The number of suggestions for the section.
220 */ 91 */
221 private int resetSection(@CategoryInt int category, @CategoryStatusEnum int categoryStatus, 92 private int resetSection(@CategoryInt int category, @CategoryStatusEnum int categoryStatus,
222 boolean alwaysAllowEmptySections) { 93 boolean alwaysAllowEmptySections) {
223 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource(); 94 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource();
224 List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCa tegory(category); 95 List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCa tegory(category);
225 SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(categor y); 96 SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(categor y);
226 97
98 SuggestionsSection section = mSections.get(category);
99
227 // Do not show an empty section if not allowed. 100 // Do not show an empty section if not allowed.
228 if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySec tions) { 101 if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySec tions) {
229 mSections.remove(category); 102 if (section != null) removeSection(section);
230 return 0; 103 return 0;
231 } 104 }
232 105
233 // Create the section if needed. 106 // Create the section if needed.
234 SuggestionsSection section = mSections.get(category);
235 if (section == null) { 107 if (section == null) {
236 section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mO fflinePageBridge); 108 section = new SuggestionsSection(this, mNewTabPageManager, mOfflineP ageBridge, info);
237 mSections.put(category, section); 109 mSections.put(category, section);
110 mChildren.add(section);
111 didAddChild(section);
238 } 112 }
239 113
240 // Add the new suggestions. 114 // Add the new suggestions.
241 setSuggestions(category, suggestions, categoryStatus); 115 setSuggestions(category, suggestions, categoryStatus);
242 116
243 return suggestions.size(); 117 return suggestions.size();
244 } 118 }
245 119
246 /** Returns callbacks to configure the interactions with the RecyclerView's items. */
247 public ItemTouchHelper.Callback getItemTouchCallbacks() {
248 return mItemTouchCallbacks;
249 }
250
251 @Override 120 @Override
252 public void onNewSuggestions(@CategoryInt int category) { 121 public void onNewSuggestions(@CategoryInt int category) {
253 @CategoryStatusEnum 122 @CategoryStatusEnum
254 int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus (category); 123 int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus (category);
255 124
256 if (!canLoadSuggestions(category, status)) return; 125 if (!canLoadSuggestions(category, status)) return;
257 126
258 // We never want to refresh the suggestions if we already have some cont ent. 127 // We never want to refresh the suggestions if we already have some cont ent.
259 if (mSections.get(category).hasSuggestions()) return; 128 if (mSections.get(category).hasSuggestions()) return;
260 129
(...skipping 30 matching lines...) Expand all
291 // The section provider has gone away. Keep open UIs as they are . 160 // The section provider has gone away. Keep open UIs as they are .
292 return; 161 return;
293 162
294 case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED: 163 case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
295 case CategoryStatus.LOADING_ERROR: 164 case CategoryStatus.LOADING_ERROR:
296 // Need to remove the entire section from the UI immediately. 165 // Need to remove the entire section from the UI immediately.
297 removeSection(mSections.get(category)); 166 removeSection(mSections.get(category));
298 return; 167 return;
299 168
300 case CategoryStatus.SIGNED_OUT: 169 case CategoryStatus.SIGNED_OUT:
301 // TODO(dgn): We currently can only reach this through an old va riation parameter. 170 resetSection(category, status, /* alwaysAllowEmptySections = */ false);
171 return;
172
302 default: 173 default:
303 mSections.get(category).setStatus(status); 174 mSections.get(category).setStatus(status);
304 return; 175 return;
305 } 176 }
306 } 177 }
307 178
308 @Override 179 @Override
309 public void onSuggestionInvalidated(@CategoryInt int category, String idWith inCategory) { 180 public void onSuggestionInvalidated(@CategoryInt int category, String idWith inCategory) {
310 if (!mSections.containsKey(category)) return; 181 if (!mSections.containsKey(category)) return;
311 mSections.get(category).removeSuggestionById(idWithinCategory); 182 mSections.get(category).removeSuggestionById(idWithinCategory);
312 } 183 }
313 184
314 @Override 185 @Override
315 public void onFullRefreshRequired() { 186 public void onFullRefreshRequired() {
316 resetSections(/*alwaysAllowEmptySections=*/false); 187 resetSections(/* alwaysAllowEmptySections = */false);
317 }
318
319 @Override
320 @ItemViewType
321 public int getItemViewType(int position) {
322 return mRoot.getItemViewType(position);
323 }
324
325 @Override
326 public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp e) {
327 assert parent == mRecyclerView;
328
329 switch (viewType) {
330 case ItemViewType.ABOVE_THE_FOLD:
331 return new NewTabPageViewHolder(mAboveTheFoldView);
332
333 case ItemViewType.HEADER:
334 return new SectionHeaderViewHolder(mRecyclerView, mUiConfig);
335
336 case ItemViewType.SNIPPET:
337 return new SnippetArticleViewHolder(mRecyclerView, mNewTabPageMa nager, mUiConfig);
338
339 case ItemViewType.SPACING:
340 return new NewTabPageViewHolder(SpacingItem.createView(parent));
341
342 case ItemViewType.STATUS:
343 return new StatusCardViewHolder(mRecyclerView, mNewTabPageManage r, mUiConfig);
344
345 case ItemViewType.PROGRESS:
346 return new ProgressViewHolder(mRecyclerView);
347
348 case ItemViewType.ACTION:
349 return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManag er, mUiConfig);
350
351 case ItemViewType.PROMO:
352 return new SignInPromo.ViewHolder(mRecyclerView, mNewTabPageMana ger, mUiConfig);
353
354 case ItemViewType.FOOTER:
355 return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
356
357 case ItemViewType.ALL_DISMISSED:
358 return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPag eManager, this);
359 }
360
361 assert false : viewType;
362 return null;
363 }
364
365 @Override
366 public void onBindViewHolder(NewTabPageViewHolder holder, final int position ) {
367 mRoot.onBindViewHolder(holder, position);
368 }
369
370 @Override
371 public int getItemCount() {
372 return mRoot.getItemCount();
373 }
374
375 public int getAboveTheFoldPosition() {
376 return getChildPositionOffset(mAboveTheFold);
377 }
378
379 public int getFirstHeaderPosition() {
380 return getFirstPositionForType(ItemViewType.HEADER);
381 }
382
383 public int getFirstCardPosition() {
384 for (int i = 0; i < getItemCount(); ++i) {
385 if (CardViewHolder.isCard(getItemViewType(i))) return i;
386 }
387 return RecyclerView.NO_POSITION;
388 }
389
390 int getBottomSpacerPosition() {
391 return getChildPositionOffset(mBottomSpacer);
392 }
393
394 int getLastContentItemPosition() {
395 return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mF ooter);
396 } 188 }
397 189
398 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions, 190 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
399 @CategoryStatusEnum int status) { 191 @CategoryStatusEnum int status) {
400 // Count the number of suggestions before this category. 192 // Count the number of suggestions before this category.
401 int globalPositionOffset = 0; 193 int globalPositionOffset = 0;
402 for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet() ) { 194 for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet() ) {
403 if (entry.getKey() == category) break; 195 if (entry.getKey() == category) break;
404 globalPositionOffset += entry.getValue().getSuggestionsCount(); 196 globalPositionOffset += entry.getValue().getSuggestionsCount();
405 } 197 }
406 // Assign global indices to the new suggestions. 198 // Assign global indices to the new suggestions.
407 for (SnippetArticle suggestion : suggestions) { 199 for (SnippetArticle suggestion : suggestions) {
408 suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosi tion; 200 suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosi tion;
409 } 201 }
410 202
411 mSections.get(category).addSuggestions(suggestions, status); 203 mSections.get(category).addSuggestions(suggestions, status);
412 } 204 }
413 205
414 private void updateChildren() {
415 mChildren.clear();
416 mChildren.add(mAboveTheFold);
417 mChildren.addAll(mSections.values());
418 mChildren.add(mSigninPromo);
419 mChildren.add(mAllDismissed);
420 mChildren.add(mFooter);
421 mChildren.add(mBottomSpacer);
422
423 updateAllDismissedVisibility();
424
425 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea st |mAboveTheFold|
426 // has not changed when refreshing from the all dismissed state.
427 notifyDataSetChanged();
428 }
429
430 private void updateAllDismissedVisibility() {
431 boolean showAllDismissed = hasAllBeenDismissed();
432 mAllDismissed.setVisible(showAllDismissed);
433 mFooter.setVisible(!showAllDismissed);
434 }
435
436 private void removeSection(SuggestionsSection section) {
437 mSections.remove(section.getCategory());
438 int startPos = getChildPositionOffset(section);
439 mChildren.remove(section);
440 notifyItemRangeRemoved(startPos, section.getItemCount());
441
442 updateAllDismissedVisibility();
443
444 notifyItemChanged(getBottomSpacerPosition());
445 }
446
447 @Override
448 public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCou nt) {
449 assert child == mRoot;
450 notifyItemRangeChanged(itemPosition, itemCount);
451 }
452
453 @Override
454 public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCo unt) {
455 assert child == mRoot;
456 notifyItemRangeInserted(itemPosition, itemCount);
457 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
458
459 updateAllDismissedVisibility();
460 }
461
462 @Override
463 public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCou nt) {
464 assert child == mRoot;
465 notifyItemRangeRemoved(itemPosition, itemCount);
466 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
467
468 updateAllDismissedVisibility();
469 }
470
471 @Override
472 public void onAttachedToRecyclerView(RecyclerView recyclerView) {
473 super.onAttachedToRecyclerView(recyclerView);
474
475 // We are assuming for now that the adapter is used with a single Recycl erView.
476 // Getting the reference as we are doing here is going to be broken if t hat changes.
477 assert mRecyclerView == null;
478
479 // FindBugs chokes on the cast below when not checked, raising BC_UNCONF IRMED_CAST
480 assert recyclerView instanceof NewTabPageRecyclerView;
481
482 mRecyclerView = (NewTabPageRecyclerView) recyclerView;
483 }
484
485 /**
486 * Dismisses the item at the provided adapter position. Can also cause the d ismissal of other
487 * items or even entire sections.
488 */
489 // TODO(crbug.com/635567): Fix this properly.
490 @SuppressLint("SwitchIntDef")
491 public void dismissItem(int position) {
492 int itemViewType = getItemViewType(position);
493
494 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st uff.
495 switch (itemViewType) {
496 case ItemViewType.STATUS:
497 case ItemViewType.ACTION:
498 dismissSection(getSuggestionsSection(position));
499 return;
500
501 case ItemViewType.SNIPPET:
502 dismissSuggestion(position);
503 return;
504
505 case ItemViewType.PROMO:
506 dismissPromo();
507 return;
508
509 default:
510 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie wType);
511 return;
512 }
513 }
514
515 private void dismissSection(SuggestionsSection section) {
516 assert SnippetsConfig.isSectionDismissalEnabled();
517
518 announceItemRemoved(section.getHeaderText());
519
520 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory());
521 removeSection(section);
522 }
523
524 private void dismissSuggestion(int position) {
525 SnippetArticle suggestion = mRoot.getSuggestionAt(position);
526 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource();
527 if (suggestionsSource == null) {
528 // It is possible for this method to be called after the NewTabPage has had destroy()
529 // called. This can happen when NewTabPageRecyclerView.dismissWithAn imation() is called
530 // and the animation ends after the user has navigated away. In this case we cannot
531 // inform the native side that the snippet has been dismissed (http: //crbug.com/649299).
532 return;
533 }
534
535 announceItemRemoved(suggestion.mTitle);
536
537 suggestionsSource.dismissSuggestion(suggestion);
538 SuggestionsSection section = getSuggestionsSection(position);
539 section.removeSuggestion(suggestion);
540 }
541
542 private void dismissPromo() {
543 announceItemRemoved(mSigninPromo.getHeader());
544 mSigninPromo.dismiss();
545 }
546
547 /**
548 * Returns another view holder that should be dismissed at the same time as the provided one.
549 */
550 public NewTabPageViewHolder getDismissSibling(ViewHolder viewHolder) {
551 int swipePos = viewHolder.getAdapterPosition();
552 int siblingPosDelta = mRoot.getDismissSiblingPosDelta(swipePos);
553 if (siblingPosDelta == 0) return null;
554
555 return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosi tion(
556 siblingPosDelta + swipePos);
557 }
558
559 private boolean hasAllBeenDismissed() {
560 return mSections.isEmpty() && !mSigninPromo.isVisible();
561 }
562
563 private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatu sEnum int status) { 206 private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatu sEnum int status) {
564 // We never want to add suggestions from unknown categories. 207 // We never want to add suggestions from unknown categories.
565 if (!mSections.containsKey(category)) return false; 208 if (!mSections.containsKey(category)) return false;
566 209
567 // The status may have changed while the suggestions were loading, perha ps they should not 210 // The status may have changed while the suggestions were loading, perha ps they should not
568 // be displayed any more. 211 // be displayed any more.
569 if (!SnippetsBridge.isCategoryEnabled(status)) { 212 if (!SnippetsBridge.isCategoryEnabled(status)) {
570 Log.w(TAG, "Received suggestions for a disabled category (id=%d, sta tus=%d)", category, 213 Log.w(TAG, "Received suggestions for a disabled category (id=%d, sta tus=%d)", category,
571 status); 214 status);
572 return false; 215 return false;
573 } 216 }
574 217
575 return true; 218 return true;
576 } 219 }
577 220
578 /** 221 /**
579 * @param itemPosition The position of an item in the adapter. 222 * Dismisses a section.
580 * @return Returns the {@link SuggestionsSection} that contains the item at 223 * @param section The section to be dismissed.
581 * {@code itemPosition}, or null if the item is not part of one.
582 */ 224 */
583 private SuggestionsSection getSuggestionsSection(int itemPosition) { 225 public void dismissSection(SuggestionsSection section) {
584 TreeNode child = mRoot.getChildForPosition(itemPosition); 226 assert SnippetsConfig.isSectionDismissalEnabled();
585 if (!(child instanceof SuggestionsSection)) return null; 227
586 return (SuggestionsSection) child; 228 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory());
229 removeSection(section);
587 } 230 }
588 231
589 private int getChildPositionOffset(TreeNode child) { 232 private void removeSection(SuggestionsSection section) {
590 return mRoot.getStartingOffsetForChild(child); 233 mSections.remove(section.getCategory());
234 willRemoveChild(section);
235 mChildren.remove(section);
591 } 236 }
592 237
593 @VisibleForTesting 238 /**
594 SnippetArticle getSuggestionAt(int position) { 239 * Restores any sections that have been dismissed and triggers a new fetch.
595 return mRoot.getSuggestionAt(position); 240 */
241 public void restoreDismissedSections() {
242 mNewTabPageManager.getSuggestionsSource().restoreDismissedCategories();
243 resetSections(/* allowEmptySections = */ true);
244 mNewTabPageManager.getSuggestionsSource().fetchRemoteSuggestions();
596 } 245 }
597 246
598 @VisibleForTesting 247 /**
599 int getFirstPositionForType(@ItemViewType int viewType) { 248 * @return Whether the list of sections is empty.
600 int count = getItemCount(); 249 */
601 for (int i = 0; i < count; i++) { 250 public boolean isEmpty() {
602 if (getItemViewType(i) == viewType) return i; 251 return mSections.isEmpty();
603 }
604 return RecyclerView.NO_POSITION;
605 } 252 }
606 253
607 SuggestionsSection getSectionForTesting(@CategoryInt int category) { 254 SuggestionsSection getSectionForTesting(@CategoryInt int categoryId) {
608 return mSections.get(category); 255 return mSections.get(categoryId);
609 }
610
611 InnerNode getRootForTesting() {
612 return mRoot;
613 }
614
615 private void announceItemRemoved(String itemTitle) {
616 // In tests the RecyclerView can be null.
617 if (mRecyclerView == null) return;
618
619 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS tring(
620 R.string.ntp_accessibility_item_removed, itemTitle));
621 }
622
623 private void announceItemRemoved(@StringRes int stringToAnnounce) {
624 // In tests the RecyclerView can be null.
625 if (mRecyclerView == null) return;
626
627 announceItemRemoved(mRecyclerView.getResources().getString(stringToAnnou nce));
628 } 256 }
629 } 257 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698