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

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java

Issue 2396523002: Unify NewTabPageItem and ItemGroup into a single tree-structured interface. (Closed)
Patch Set: review Created 4 years, 2 months 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; 7 import android.annotation.SuppressLint;
8 import android.graphics.Canvas; 8 import android.graphics.Canvas;
9 import android.support.v7.widget.RecyclerView; 9 import android.support.v7.widget.RecyclerView;
10 import android.support.v7.widget.RecyclerView.Adapter; 10 import android.support.v7.widget.RecyclerView.Adapter;
11 import android.support.v7.widget.RecyclerView.ViewHolder; 11 import android.support.v7.widget.RecyclerView.ViewHolder;
12 import android.support.v7.widget.helper.ItemTouchHelper; 12 import android.support.v7.widget.helper.ItemTouchHelper;
13 import android.view.View; 13 import android.view.View;
14 import android.view.ViewGroup; 14 import android.view.ViewGroup;
15 15
16 import org.chromium.base.Log; 16 import org.chromium.base.Log;
17 import org.chromium.base.VisibleForTesting; 17 import org.chromium.base.VisibleForTesting;
18 import org.chromium.chrome.R; 18 import org.chromium.chrome.R;
19 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; 19 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
20 import org.chromium.chrome.browser.ntp.UiConfig; 20 import org.chromium.chrome.browser.ntp.UiConfig;
21 import org.chromium.chrome.browser.ntp.snippets.CategoryInt; 21 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
22 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; 22 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
23 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu m; 23 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu m;
24 import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
25 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; 24 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
26 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; 25 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
27 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; 26 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
28 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; 27 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
29 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; 28 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
30 import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver; 29 import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
31 30
32 import java.util.ArrayList; 31 import java.util.ArrayList;
33 import java.util.Collections; 32 import java.util.Collections;
34 import java.util.LinkedHashMap; 33 import java.util.LinkedHashMap;
35 import java.util.List; 34 import java.util.List;
36 import java.util.Map; 35 import java.util.Map;
37 36
38 /** 37 /**
39 * A class that handles merging above the fold elements and below the fold cards into an adapter 38 * A class that handles merging above the fold elements and below the fold cards into an adapter
40 * that will be used to back the NTP RecyclerView. The first element in the adap ter should always be 39 * that will be used to back the NTP RecyclerView. The first element in the adap ter should always be
41 * the above-the-fold view (containing the logo, search box, and most visited ti les) and subsequent 40 * the above-the-fold view (containing the logo, search box, and most visited ti les) and subsequent
42 * elements will be the cards shown to the user 41 * elements will be the cards shown to the user
43 */ 42 */
44 public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> 43 public class NewTabPageAdapter
45 implements SuggestionsSource.Observer, ItemGroup.Observer { 44 extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Obser ver, NodeParent {
46 private static final String TAG = "Ntp"; 45 private static final String TAG = "Ntp";
47 46
48 private final NewTabPageManager mNewTabPageManager; 47 private final NewTabPageManager mNewTabPageManager;
49 private final View mAboveTheFoldView; 48 private final View mAboveTheFoldView;
50 private final UiConfig mUiConfig; 49 private final UiConfig mUiConfig;
51 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback s(); 50 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback s();
52 private NewTabPageRecyclerView mRecyclerView; 51 private NewTabPageRecyclerView mRecyclerView;
53 52
54 /** 53 /**
55 * List of all item groups (which can themselves contain multiple items. Whe n flattened, this 54 * List of all item groups (which can themselves contain multiple items. Whe n flattened, this
56 * will be a list of all items the adapter exposes. 55 * will be a list of all items the adapter exposes.
57 */ 56 */
58 private final List<ItemGroup> mGroups = new ArrayList<>(); 57 private final List<TreeNode> mGroups = new ArrayList<>();
59 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); 58 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
60 private final SignInPromo mSigninPromo = new SignInPromo(); 59 private final SignInPromo mSigninPromo;
61 private final AllDismissedItem mAllDismissed = new AllDismissedItem(); 60 private final AllDismissedItem mAllDismissed = new AllDismissedItem();
62 private final Footer mFooter = new Footer(); 61 private final Footer mFooter = new Footer();
63 private final SpacingItem mBottomSpacer = new SpacingItem(); 62 private final SpacingItem mBottomSpacer = new SpacingItem();
63 private final InnerNode mRoot;
64 64
65 /** Maps suggestion categories to sections, with stable iteration ordering. */ 65 /** Maps suggestion categories to sections, with stable iteration ordering. */
66 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap <>(); 66 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap <>();
67 67
68 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { 68 private class ItemTouchCallbacks extends ItemTouchHelper.Callback {
69 @Override 69 @Override
70 public void onSwiped(ViewHolder viewHolder, int direction) { 70 public void onSwiped(ViewHolder viewHolder, int direction) {
71 mRecyclerView.onItemDismissStarted(viewHolder); 71 mRecyclerView.onItemDismissStarted(viewHolder);
72 NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition()); 72 NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition());
73 } 73 }
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
127 * 127 *
128 * @param manager the NewTabPageManager to use to interact with the rest of the system. 128 * @param manager the NewTabPageManager to use to interact with the rest of the system.
129 * @param aboveTheFoldView the layout encapsulating all the above-the-fold e lements 129 * @param aboveTheFoldView the layout encapsulating all the above-the-fold e lements
130 * (logo, search box, most visited tiles) 130 * (logo, search box, most visited tiles)
131 * @param uiConfig the NTP UI configuration, to be passed to created views. 131 * @param uiConfig the NTP UI configuration, to be passed to created views.
132 */ 132 */
133 public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, U iConfig uiConfig) { 133 public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, U iConfig uiConfig) {
134 mNewTabPageManager = manager; 134 mNewTabPageManager = manager;
135 mAboveTheFoldView = aboveTheFoldView; 135 mAboveTheFoldView = aboveTheFoldView;
136 mUiConfig = uiConfig; 136 mUiConfig = uiConfig;
137 mSigninPromo.setObserver(this); 137 mRoot = new InnerNode(this) {
138 @Override
139 protected List<TreeNode> getChildren() {
140 return mGroups;
141 }
142 };
143
144 mSigninPromo = new SignInPromo(mRoot);
138 resetSections(/*alwaysAllowEmptySections=*/false); 145 resetSections(/*alwaysAllowEmptySections=*/false);
139 mNewTabPageManager.getSuggestionsSource().setObserver(this); 146 mNewTabPageManager.getSuggestionsSource().setObserver(this);
140 147
141 mNewTabPageManager.registerSignInStateObserver(new SignInStateObserver() { 148 mNewTabPageManager.registerSignInStateObserver(new SignInStateObserver() {
142 @Override 149 @Override
143 public void onSignedIn() { 150 public void onSignedIn() {
144 mSigninPromo.hide(); 151 mSigninPromo.hide();
145 resetSections(/*alwaysAllowEmptySections=*/false); 152 resetSections(/*alwaysAllowEmptySections=*/false);
146 } 153 }
147 154
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 205
199 // Do not show an empty section if not allowed. 206 // Do not show an empty section if not allowed.
200 if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySec tions) { 207 if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySec tions) {
201 mSections.remove(category); 208 mSections.remove(category);
202 return 0; 209 return 0;
203 } 210 }
204 211
205 // Create the section if needed. 212 // Create the section if needed.
206 SuggestionsSection section = mSections.get(category); 213 SuggestionsSection section = mSections.get(category);
207 if (section == null) { 214 if (section == null) {
208 section = new SuggestionsSection(info, this); 215 section = new SuggestionsSection(mRoot, info);
209 mSections.put(category, section); 216 mSections.put(category, section);
210 } 217 }
211 218
212 // Add the new suggestions. 219 // Add the new suggestions.
213 setSuggestions(category, suggestions, categoryStatus); 220 setSuggestions(category, suggestions, categoryStatus);
214 221
215 return suggestions.size(); 222 return suggestions.size();
216 } 223 }
217 224
218 /** Returns callbacks to configure the interactions with the RecyclerView's items. */ 225 /** Returns callbacks to configure the interactions with the RecyclerView's items. */
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 } 285 }
279 } 286 }
280 287
281 @Override 288 @Override
282 public void onSuggestionInvalidated(@CategoryInt int category, String idWith inCategory) { 289 public void onSuggestionInvalidated(@CategoryInt int category, String idWith inCategory) {
283 if (!mSections.containsKey(category)) return; 290 if (!mSections.containsKey(category)) return;
284 mSections.get(category).removeSuggestionById(idWithinCategory); 291 mSections.get(category).removeSuggestionById(idWithinCategory);
285 } 292 }
286 293
287 @Override 294 @Override
288 @NewTabPageItem.ViewType 295 @ItemViewType
289 public int getItemViewType(int position) { 296 public int getItemViewType(int position) {
290 return getItems().get(position).getType(); 297 return mRoot.getItemViewType(position);
291 } 298 }
292 299
293 @Override 300 @Override
294 public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp e) { 301 public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp e) {
295 assert parent == mRecyclerView; 302 assert parent == mRecyclerView;
296 303
297 if (viewType == NewTabPageItem.VIEW_TYPE_ABOVE_THE_FOLD) { 304 switch (viewType) {
298 return new NewTabPageViewHolder(mAboveTheFoldView); 305 case ItemViewType.ABOVE_THE_FOLD: {
dgn 2016/10/17 09:58:01 optional nit: why the brackets?
Bernhard Bauer 2016/10/17 14:33:46 Hm, I thought the brace-less style would only be a
306 return new NewTabPageViewHolder(mAboveTheFoldView);
307 }
308 case ItemViewType.HEADER: {
309 return new SectionHeaderViewHolder(mRecyclerView, mUiConfig);
310 }
311 case ItemViewType.SNIPPET: {
312 return new SnippetArticleViewHolder(mRecyclerView, mNewTabPageMa nager, mUiConfig);
313 }
314 case ItemViewType.SPACING: {
315 return new NewTabPageViewHolder(SpacingItem.createView(parent));
316 }
317 case ItemViewType.STATUS: {
318 return new StatusCardViewHolder(mRecyclerView, mUiConfig);
319 }
320 case ItemViewType.PROGRESS: {
321 return new ProgressViewHolder(mRecyclerView);
322 }
323 case ItemViewType.ACTION: {
324 return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManag er, mUiConfig);
325 }
326 case ItemViewType.PROMO: {
327 return new SignInPromo.ViewHolder(mRecyclerView, mUiConfig);
328 }
329 case ItemViewType.FOOTER: {
330 return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
331 }
332 case ItemViewType.ALL_DISMISSED: {
333 return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPag eManager, this);
334 }
299 } 335 }
300 336
301 if (viewType == NewTabPageItem.VIEW_TYPE_HEADER) { 337 assert false : viewType;
302 return new SectionHeaderViewHolder(mRecyclerView, mUiConfig);
303 }
304
305 if (viewType == NewTabPageItem.VIEW_TYPE_SNIPPET) {
306 return new SnippetArticleViewHolder(mRecyclerView, mNewTabPageManage r, mUiConfig);
307 }
308
309 if (viewType == NewTabPageItem.VIEW_TYPE_SPACING) {
310 return new NewTabPageViewHolder(SpacingItem.createView(parent));
311 }
312
313 if (viewType == NewTabPageItem.VIEW_TYPE_STATUS) {
314 return new StatusCardViewHolder(mRecyclerView, mUiConfig);
315 }
316
317 if (viewType == NewTabPageItem.VIEW_TYPE_PROGRESS) {
318 return new ProgressViewHolder(mRecyclerView);
319 }
320
321 if (viewType == NewTabPageItem.VIEW_TYPE_ACTION) {
322 return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig);
323 }
324
325 if (viewType == NewTabPageItem.VIEW_TYPE_PROMO) {
326 return new SignInPromo.ViewHolder(mRecyclerView, mUiConfig);
327 }
328
329 if (viewType == NewTabPageItem.VIEW_TYPE_FOOTER) {
330 return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
331 }
332
333 if (viewType == NewTabPageItem.VIEW_TYPE_ALL_DISMISSED) {
334 return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPageMan ager, this);
335 }
336
337 return null; 338 return null;
338 } 339 }
339 340
340 @Override 341 @Override
341 public void onBindViewHolder(NewTabPageViewHolder holder, final int position ) { 342 public void onBindViewHolder(NewTabPageViewHolder holder, final int position ) {
342 getItems().get(position).onBindViewHolder(holder); 343 mRoot.onBindViewHolder(holder, position);
343 } 344 }
344 345
345 @Override 346 @Override
346 public int getItemCount() { 347 public int getItemCount() {
347 return getItems().size(); 348 return mRoot.getItemCount();
348 } 349 }
349 350
350 public int getAboveTheFoldPosition() { 351 public int getAboveTheFoldPosition() {
351 return getGroupPositionOffset(mAboveTheFold); 352 return getGroupPositionOffset(mAboveTheFold);
352 } 353 }
353 354
354 public int getFirstHeaderPosition() { 355 public int getFirstHeaderPosition() {
355 List<NewTabPageItem> items = getItems(); 356 int count = getItemCount();
356 for (int i = 0; i < items.size(); i++) { 357 for (int i = 0; i < count; i++) {
357 if (items.get(i) instanceof SectionHeader) return i; 358 if (getItemViewType(i) == ItemViewType.HEADER) return i;
358 } 359 }
359 return RecyclerView.NO_POSITION; 360 return RecyclerView.NO_POSITION;
360 } 361 }
361 362
362 public int getFirstCardPosition() { 363 public int getFirstCardPosition() {
363 for (int i = 0; i < getItemCount(); ++i) { 364 for (int i = 0; i < getItemCount(); ++i) {
364 if (CardViewHolder.isCard(getItemViewType(i))) return i; 365 if (CardViewHolder.isCard(getItemViewType(i))) return i;
365 } 366 }
366 return RecyclerView.NO_POSITION; 367 return RecyclerView.NO_POSITION;
367 } 368 }
368 369
369 public int getFooterPosition() { 370 public int getFooterPosition() {
370 return getGroupPositionOffset(mFooter); 371 return getGroupPositionOffset(mFooter);
371 } 372 }
372 373
373 public int getBottomSpacerPosition() { 374 public int getBottomSpacerPosition() {
374 return getGroupPositionOffset(mBottomSpacer); 375 return getGroupPositionOffset(mBottomSpacer);
375 } 376 }
376 377
377 public int getSuggestionPosition(SnippetArticle article) { 378 public int getSuggestionPosition(SnippetArticle article) {
378 List<NewTabPageItem> items = getItems(); 379 for (int i = 0; i < mRoot.getItemCount(); i++) {
379 for (int i = 0; i < items.size(); i++) { 380 SnippetArticle articleToCheck = mRoot.getSuggestionAt(i);
380 NewTabPageItem item = items.get(i); 381 if (articleToCheck != null && articleToCheck.equals(article)) return i;
381 if (article.equals(item)) return i;
382 } 382 }
383 return RecyclerView.NO_POSITION; 383 return RecyclerView.NO_POSITION;
384 } 384 }
385 385
386 /** Start a request for new snippets. */ 386 /** Start a request for new snippets. */
387 public void reloadSnippets() { 387 public void reloadSnippets() {
388 SnippetsBridge.fetchSnippets(/*forceRequest=*/true); 388 SnippetsBridge.fetchSnippets(/*forceRequest=*/true);
389 } 389 }
390 390
391 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions, 391 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
(...skipping 22 matching lines...) Expand all
414 414
415 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea st |mAboveTheFold| 415 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea st |mAboveTheFold|
416 // has not changed when refreshing from the all dismissed state. 416 // has not changed when refreshing from the all dismissed state.
417 notifyDataSetChanged(); 417 notifyDataSetChanged();
418 } 418 }
419 419
420 private void removeSection(SuggestionsSection section) { 420 private void removeSection(SuggestionsSection section) {
421 mSections.remove(section.getCategory()); 421 mSections.remove(section.getCategory());
422 int startPos = getGroupPositionOffset(section); 422 int startPos = getGroupPositionOffset(section);
423 mGroups.remove(section); 423 mGroups.remove(section);
424 notifyItemRangeRemoved(startPos, section.getItems().size()); 424 notifyItemRangeRemoved(startPos, section.getItemCount());
425 425
426 if (hasAllBeenDismissed()) { 426 if (hasAllBeenDismissed()) {
427 int footerPosition = getFooterPosition(); 427 int footerPosition = getFooterPosition();
428 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed); 428 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed);
429 notifyItemChanged(footerPosition); 429 notifyItemChanged(footerPosition);
430 } 430 }
431 431
432 notifyItemChanged(getBottomSpacerPosition()); 432 notifyItemChanged(getBottomSpacerPosition());
433 } 433 }
434 434
435 @Override 435 @Override
436 public void onItemRangeChanged(ItemGroup group, int itemPosition, int itemCo unt) { 436 public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCou nt) {
437 assert child == mRoot;
437 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 438 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
438 notifyItemRangeChanged(getGroupPositionOffset(group) + itemPosition, ite mCount); 439 notifyItemRangeChanged(itemPosition, itemCount);
439 } 440 }
440 441
441 @Override 442 @Override
442 public void onItemRangeInserted(ItemGroup group, int itemPosition, int itemC ount) { 443 public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCo unt) {
444 assert child == mRoot;
443 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 445 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
444 notifyItemRangeInserted(getGroupPositionOffset(group) + itemPosition, it emCount); 446 notifyItemRangeInserted(itemPosition, itemCount);
445 notifyItemChanged(getItems().size() - 1); // Refresh the spacer too. 447 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
446 } 448 }
447 449
448 @Override 450 @Override
449 public void onItemRangeRemoved(ItemGroup group, int itemPosition, int itemCo unt) { 451 public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCou nt) {
452 assert child == mRoot;
450 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 453 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
451 notifyItemRangeRemoved(getGroupPositionOffset(group) + itemPosition, ite mCount); 454 notifyItemRangeRemoved(itemPosition, itemCount);
452 notifyItemChanged(getItems().size() - 1); // Refresh the spacer too. 455 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
453 } 456 }
454 457
455 @Override 458 @Override
456 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 459 public void onAttachedToRecyclerView(RecyclerView recyclerView) {
457 super.onAttachedToRecyclerView(recyclerView); 460 super.onAttachedToRecyclerView(recyclerView);
458 461
459 // We are assuming for now that the adapter is used with a single Recycl erView. 462 // We are assuming for now that the adapter is used with a single Recycl erView.
460 // Getting the reference as we are doing here is going to be broken if t hat changes. 463 // Getting the reference as we are doing here is going to be broken if t hat changes.
461 assert mRecyclerView == null; 464 assert mRecyclerView == null;
462 465
463 // FindBugs chokes on the cast below when not checked, raising BC_UNCONF IRMED_CAST 466 // FindBugs chokes on the cast below when not checked, raising BC_UNCONF IRMED_CAST
464 assert recyclerView instanceof NewTabPageRecyclerView; 467 assert recyclerView instanceof NewTabPageRecyclerView;
465 468
466 mRecyclerView = (NewTabPageRecyclerView) recyclerView; 469 mRecyclerView = (NewTabPageRecyclerView) recyclerView;
467 } 470 }
468 471
469 /** 472 /**
470 * Dismisses the item at the provided adapter position. Can also cause the d ismissal of other 473 * Dismisses the item at the provided adapter position. Can also cause the d ismissal of other
471 * items or even entire sections. 474 * items or even entire sections.
472 */ 475 */
473 // TODO(crbug.com/635567): Fix this properly. 476 // TODO(crbug.com/635567): Fix this properly.
474 @SuppressLint("SwitchIntDef") 477 @SuppressLint("SwitchIntDef")
475 public void dismissItem(int position) { 478 public void dismissItem(int position) {
476 int itemViewType = getItemViewType(position); 479 int itemViewType = getItemViewType(position);
477 480
478 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st uff. 481 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st uff.
479 switch (itemViewType) { 482 switch (itemViewType) {
480 case NewTabPageItem.VIEW_TYPE_STATUS: 483 case ItemViewType.STATUS:
481 case NewTabPageItem.VIEW_TYPE_ACTION: 484 case ItemViewType.ACTION:
482 dismissSection((SuggestionsSection) getGroup(position)); 485 dismissSection(getSuggestionsSection(position));
483 return; 486 return;
484 487
485 case NewTabPageItem.VIEW_TYPE_SNIPPET: 488 case ItemViewType.SNIPPET:
486 dismissSuggestion(position); 489 dismissSuggestion(position);
487 return; 490 return;
488 491
489 case NewTabPageItem.VIEW_TYPE_PROMO: 492 case ItemViewType.PROMO:
490 dismissPromo(); 493 dismissPromo();
491 return; 494 return;
492 495
493 default: 496 default:
494 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie wType); 497 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie wType);
495 return; 498 return;
496 } 499 }
497 } 500 }
498 501
499 private void dismissSection(SuggestionsSection section) { 502 private void dismissSection(SuggestionsSection section) {
500 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory()); 503 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory());
501 removeSection(section); 504 removeSection(section);
502 } 505 }
503 506
504 private void dismissSuggestion(int position) { 507 private void dismissSuggestion(int position) {
505 SnippetArticle suggestion = (SnippetArticle) getItems().get(position); 508 SnippetArticle suggestion = mRoot.getSuggestionAt(position);
506
507 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource(); 509 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource();
508 if (suggestionsSource == null) { 510 if (suggestionsSource == null) {
509 // It is possible for this method to be called after the NewTabPage has had destroy() 511 // It is possible for this method to be called after the NewTabPage has had destroy()
510 // called. This can happen when NewTabPageRecyclerView.dismissWithAn imation() is called 512 // called. This can happen when NewTabPageRecyclerView.dismissWithAn imation() is called
511 // and the animation ends after the user has navigated away. In this case we cannot 513 // and the animation ends after the user has navigated away. In this case we cannot
512 // inform the native side that the snippet has been dismissed (http: //crbug.com/649299). 514 // inform the native side that the snippet has been dismissed (http: //crbug.com/649299).
513 return; 515 return;
514 } 516 }
515 517
516 announceItemRemoved(suggestion.mTitle); 518 announceItemRemoved(suggestion.mTitle);
517 519
518 suggestionsSource.dismissSuggestion(suggestion); 520 suggestionsSource.dismissSuggestion(suggestion);
519 SuggestionsSection section = (SuggestionsSection) getGroup(position); 521 SuggestionsSection section = getSuggestionsSection(position);
520 section.removeSuggestion(suggestion); 522 section.removeSuggestion(suggestion);
521 } 523 }
522 524
523 private void dismissPromo() { 525 private void dismissPromo() {
524 // TODO(dgn): accessibility announcement. 526 // TODO(dgn): accessibility announcement.
525 mSigninPromo.dismiss(); 527 mSigninPromo.dismiss();
526 528
527 if (hasAllBeenDismissed()) { 529 if (hasAllBeenDismissed()) {
528 int footerPosition = getFooterPosition(); 530 int footerPosition = getFooterPosition();
529 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed); 531 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed);
530 notifyItemChanged(footerPosition); 532 notifyItemChanged(footerPosition);
531 } 533 }
532 } 534 }
533 535
534 /** 536 /**
535 * Returns an unmodifiable list containing all items in the adapter.
536 */
537 private List<NewTabPageItem> getItems() {
538 List<NewTabPageItem> items = new ArrayList<>();
539 for (ItemGroup group : mGroups) {
540 items.addAll(group.getItems());
541 }
542 return Collections.unmodifiableList(items);
543 }
544
545 /**
546 * Returns another view holder that should be dismissed at the same time as the provided one. 537 * Returns another view holder that should be dismissed at the same time as the provided one.
547 */ 538 */
548 public ViewHolder getDismissSibling(ViewHolder viewHolder) { 539 public ViewHolder getDismissSibling(ViewHolder viewHolder) {
549 int swipePos = viewHolder.getAdapterPosition(); 540 int swipePos = viewHolder.getAdapterPosition();
550 ItemGroup group = getGroup(swipePos); 541 SuggestionsSection section = getSuggestionsSection(swipePos);
542 if (section == null) return null;
551 543
552 if (!(group instanceof SuggestionsSection)) return null; 544 int siblingPosDelta =
553 545 section.getDismissSiblingPosDelta(swipePos - getGroupPositionOff set(section));
554 SuggestionsSection section = (SuggestionsSection) group;
555 int siblingPosDelta = section.getDismissSiblingPosDelta(getItems().get(s wipePos));
556 if (siblingPosDelta == 0) return null; 546 if (siblingPosDelta == 0) return null;
557 547
558 return mRecyclerView.findViewHolderForAdapterPosition(siblingPosDelta + swipePos); 548 return mRecyclerView.findViewHolderForAdapterPosition(siblingPosDelta + swipePos);
559 } 549 }
560 550
561 private boolean hasAllBeenDismissed() { 551 private boolean hasAllBeenDismissed() {
562 return mSections.isEmpty() && !mSigninPromo.isShown(); 552 return mSections.isEmpty() && !mSigninPromo.isShown();
563 } 553 }
564 554
555 /**
556 * @param itemPosition The position of an item in the adapter.
557 * @return Returns the {@link SuggestionsSection} that contains the item at
558 * {@code itemPosition}, or null if the item is not part of one.
559 */
565 @VisibleForTesting 560 @VisibleForTesting
566 ItemGroup getGroup(int itemPosition) { 561 SuggestionsSection getSuggestionsSection(int itemPosition) {
567 int itemsSkipped = 0; 562 TreeNode child = mGroups.get(mRoot.getChildIndexForPosition(itemPosition ));
568 for (ItemGroup group : mGroups) { 563 if (!(child instanceof SuggestionsSection)) return null;
569 List<NewTabPageItem> items = group.getItems(); 564 return (SuggestionsSection) child;
570 itemsSkipped += items.size();
571 if (itemPosition < itemsSkipped) return group;
572 }
573 return null;
574 } 565 }
575 566
576 @VisibleForTesting 567 @VisibleForTesting
577 List<ItemGroup> getGroups() { 568 List<TreeNode> getGroups() {
578 return Collections.unmodifiableList(mGroups); 569 return Collections.unmodifiableList(mGroups);
579 } 570 }
580 571
581 @VisibleForTesting 572 @VisibleForTesting
582 int getGroupPositionOffset(ItemGroup group) { 573 int getGroupPositionOffset(TreeNode group) {
583 int positionOffset = 0; 574 return mRoot.getStartingOffsetForChild(group);
584 for (ItemGroup candidateGroup : mGroups) {
585 if (candidateGroup == group) return positionOffset;
586 positionOffset += candidateGroup.getItems().size();
587 }
588 Log.d(TAG, "Group not found: %s", group);
589 return RecyclerView.NO_POSITION;
590 } 575 }
591 576
592 @VisibleForTesting 577 @VisibleForTesting
593 SnippetArticle getSuggestionAt(int position) { 578 SnippetArticle getSuggestionAt(int position) {
594 return (SnippetArticle) getItems().get(position); 579 return mRoot.getSuggestionAt(position);
595 } 580 }
596 581
597 private void announceItemRemoved(String suggestionTitle) { 582 private void announceItemRemoved(String suggestionTitle) {
598 // In tests the RecyclerView can be null. 583 // In tests the RecyclerView can be null.
599 if (mRecyclerView == null) return; 584 if (mRecyclerView == null) return;
600 585
601 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS tring( 586 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS tring(
602 R.string.ntp_accessibility_item_removed, suggestionTitle)); 587 R.string.ntp_accessibility_item_removed, suggestionTitle));
603 } 588 }
604 } 589 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698