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

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:
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);
299 } 334 }
300 335
301 if (viewType == NewTabPageItem.VIEW_TYPE_HEADER) { 336 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; 337 return null;
338 } 338 }
339 339
340 @Override 340 @Override
341 public void onBindViewHolder(NewTabPageViewHolder holder, final int position ) { 341 public void onBindViewHolder(NewTabPageViewHolder holder, final int position ) {
342 getItems().get(position).onBindViewHolder(holder); 342 mRoot.onBindViewHolder(holder, position);
343 } 343 }
344 344
345 @Override 345 @Override
346 public int getItemCount() { 346 public int getItemCount() {
347 return getItems().size(); 347 return mRoot.getItemCount();
348 } 348 }
349 349
350 public int getAboveTheFoldPosition() { 350 public int getAboveTheFoldPosition() {
351 return getGroupPositionOffset(mAboveTheFold); 351 return getGroupPositionOffset(mAboveTheFold);
352 } 352 }
353 353
354 public int getFirstHeaderPosition() { 354 public int getFirstHeaderPosition() {
355 List<NewTabPageItem> items = getItems(); 355 int count = getItemCount();
356 for (int i = 0; i < items.size(); i++) { 356 for (int i = 0; i < count; i++) {
357 if (items.get(i) instanceof SectionHeader) return i; 357 if (getItemViewType(i) == ItemViewType.HEADER) return i;
358 } 358 }
359 return RecyclerView.NO_POSITION; 359 return RecyclerView.NO_POSITION;
360 } 360 }
361 361
362 public int getFirstCardPosition() { 362 public int getFirstCardPosition() {
363 for (int i = 0; i < getItemCount(); ++i) { 363 for (int i = 0; i < getItemCount(); ++i) {
364 if (CardViewHolder.isCard(getItemViewType(i))) return i; 364 if (CardViewHolder.isCard(getItemViewType(i))) return i;
365 } 365 }
366 return RecyclerView.NO_POSITION; 366 return RecyclerView.NO_POSITION;
367 } 367 }
368 368
369 public int getFooterPosition() { 369 public int getFooterPosition() {
370 return getGroupPositionOffset(mFooter); 370 return getGroupPositionOffset(mFooter);
371 } 371 }
372 372
373 public int getBottomSpacerPosition() { 373 public int getBottomSpacerPosition() {
374 return getGroupPositionOffset(mBottomSpacer); 374 return getGroupPositionOffset(mBottomSpacer);
375 } 375 }
376 376
377 public int getLastContentItemPosition() { 377 public int getLastContentItemPosition() {
378 return getGroupPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mF ooter); 378 return getGroupPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mF ooter);
379 } 379 }
380 380
381 public int getSuggestionPosition(SnippetArticle article) { 381 public int getSuggestionPosition(SnippetArticle article) {
382 List<NewTabPageItem> items = getItems(); 382 for (int i = 0; i < mRoot.getItemCount(); i++) {
383 for (int i = 0; i < items.size(); i++) { 383 SnippetArticle articleToCheck = mRoot.getSuggestionAt(i);
384 NewTabPageItem item = items.get(i); 384 if (articleToCheck != null && articleToCheck.equals(article)) return i;
385 if (article.equals(item)) return i;
386 } 385 }
387 return RecyclerView.NO_POSITION; 386 return RecyclerView.NO_POSITION;
388 } 387 }
389 388
390 /** Start a request for new snippets. */ 389 /** Start a request for new snippets. */
391 public void reloadSnippets() { 390 public void reloadSnippets() {
392 SnippetsBridge.fetchSnippets(/*forceRequest=*/true); 391 SnippetsBridge.fetchSnippets(/*forceRequest=*/true);
393 } 392 }
394 393
395 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions, 394 private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
(...skipping 22 matching lines...) Expand all
418 417
419 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea st |mAboveTheFold| 418 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea st |mAboveTheFold|
420 // has not changed when refreshing from the all dismissed state. 419 // has not changed when refreshing from the all dismissed state.
421 notifyDataSetChanged(); 420 notifyDataSetChanged();
422 } 421 }
423 422
424 private void removeSection(SuggestionsSection section) { 423 private void removeSection(SuggestionsSection section) {
425 mSections.remove(section.getCategory()); 424 mSections.remove(section.getCategory());
426 int startPos = getGroupPositionOffset(section); 425 int startPos = getGroupPositionOffset(section);
427 mGroups.remove(section); 426 mGroups.remove(section);
428 notifyItemRangeRemoved(startPos, section.getItems().size()); 427 notifyItemRangeRemoved(startPos, section.getItemCount());
429 428
430 if (hasAllBeenDismissed()) { 429 if (hasAllBeenDismissed()) {
431 int footerPosition = getFooterPosition(); 430 int footerPosition = getFooterPosition();
432 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed); 431 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed);
433 notifyItemChanged(footerPosition); 432 notifyItemChanged(footerPosition);
434 } 433 }
435 434
436 notifyItemChanged(getBottomSpacerPosition()); 435 notifyItemChanged(getBottomSpacerPosition());
437 } 436 }
438 437
439 @Override 438 @Override
440 public void onItemRangeChanged(ItemGroup group, int itemPosition, int itemCo unt) { 439 public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCou nt) {
440 assert child == mRoot;
441 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 441 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
442 notifyItemRangeChanged(getGroupPositionOffset(group) + itemPosition, ite mCount); 442 notifyItemRangeChanged(itemPosition, itemCount);
443 } 443 }
444 444
445 @Override 445 @Override
446 public void onItemRangeInserted(ItemGroup group, int itemPosition, int itemC ount) { 446 public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCo unt) {
447 assert child == mRoot;
447 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 448 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
448 notifyItemRangeInserted(getGroupPositionOffset(group) + itemPosition, it emCount); 449 notifyItemRangeInserted(itemPosition, itemCount);
449 notifyItemChanged(getItems().size() - 1); // Refresh the spacer too. 450 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
450 } 451 }
451 452
452 @Override 453 @Override
453 public void onItemRangeRemoved(ItemGroup group, int itemPosition, int itemCo unt) { 454 public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCou nt) {
455 assert child == mRoot;
454 if (mGroups.isEmpty()) return; // The sections have not been initialised yet. 456 if (mGroups.isEmpty()) return; // The sections have not been initialised yet.
455 notifyItemRangeRemoved(getGroupPositionOffset(group) + itemPosition, ite mCount); 457 notifyItemRangeRemoved(itemPosition, itemCount);
456 notifyItemChanged(getItems().size() - 1); // Refresh the spacer too. 458 notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
457 } 459 }
458 460
459 @Override 461 @Override
460 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 462 public void onAttachedToRecyclerView(RecyclerView recyclerView) {
461 super.onAttachedToRecyclerView(recyclerView); 463 super.onAttachedToRecyclerView(recyclerView);
462 464
463 // We are assuming for now that the adapter is used with a single Recycl erView. 465 // We are assuming for now that the adapter is used with a single Recycl erView.
464 // Getting the reference as we are doing here is going to be broken if t hat changes. 466 // Getting the reference as we are doing here is going to be broken if t hat changes.
465 assert mRecyclerView == null; 467 assert mRecyclerView == null;
466 468
467 // FindBugs chokes on the cast below when not checked, raising BC_UNCONF IRMED_CAST 469 // FindBugs chokes on the cast below when not checked, raising BC_UNCONF IRMED_CAST
468 assert recyclerView instanceof NewTabPageRecyclerView; 470 assert recyclerView instanceof NewTabPageRecyclerView;
469 471
470 mRecyclerView = (NewTabPageRecyclerView) recyclerView; 472 mRecyclerView = (NewTabPageRecyclerView) recyclerView;
471 } 473 }
472 474
473 /** 475 /**
474 * Dismisses the item at the provided adapter position. Can also cause the d ismissal of other 476 * Dismisses the item at the provided adapter position. Can also cause the d ismissal of other
475 * items or even entire sections. 477 * items or even entire sections.
476 */ 478 */
477 // TODO(crbug.com/635567): Fix this properly. 479 // TODO(crbug.com/635567): Fix this properly.
478 @SuppressLint("SwitchIntDef") 480 @SuppressLint("SwitchIntDef")
479 public void dismissItem(int position) { 481 public void dismissItem(int position) {
480 int itemViewType = getItemViewType(position); 482 int itemViewType = getItemViewType(position);
481 483
482 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st uff. 484 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st uff.
483 switch (itemViewType) { 485 switch (itemViewType) {
484 case NewTabPageItem.VIEW_TYPE_STATUS: 486 case ItemViewType.STATUS:
485 case NewTabPageItem.VIEW_TYPE_ACTION: 487 case ItemViewType.ACTION:
486 dismissSection((SuggestionsSection) getGroup(position)); 488 dismissSection(getSuggestionsSection(position));
487 return; 489 return;
488 490
489 case NewTabPageItem.VIEW_TYPE_SNIPPET: 491 case ItemViewType.SNIPPET:
490 dismissSuggestion(position); 492 dismissSuggestion(position);
491 return; 493 return;
492 494
493 case NewTabPageItem.VIEW_TYPE_PROMO: 495 case ItemViewType.PROMO:
494 dismissPromo(); 496 dismissPromo();
495 return; 497 return;
496 498
497 default: 499 default:
498 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie wType); 500 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie wType);
499 return; 501 return;
500 } 502 }
501 } 503 }
502 504
503 private void dismissSection(SuggestionsSection section) { 505 private void dismissSection(SuggestionsSection section) {
504 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory()); 506 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat egory());
505 removeSection(section); 507 removeSection(section);
506 } 508 }
507 509
508 private void dismissSuggestion(int position) { 510 private void dismissSuggestion(int position) {
509 SnippetArticle suggestion = (SnippetArticle) getItems().get(position); 511 SnippetArticle suggestion = mRoot.getSuggestionAt(position);
510
511 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource(); 512 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS ource();
512 if (suggestionsSource == null) { 513 if (suggestionsSource == null) {
513 // It is possible for this method to be called after the NewTabPage has had destroy() 514 // It is possible for this method to be called after the NewTabPage has had destroy()
514 // called. This can happen when NewTabPageRecyclerView.dismissWithAn imation() is called 515 // called. This can happen when NewTabPageRecyclerView.dismissWithAn imation() is called
515 // and the animation ends after the user has navigated away. In this case we cannot 516 // and the animation ends after the user has navigated away. In this case we cannot
516 // inform the native side that the snippet has been dismissed (http: //crbug.com/649299). 517 // inform the native side that the snippet has been dismissed (http: //crbug.com/649299).
517 return; 518 return;
518 } 519 }
519 520
520 announceItemRemoved(suggestion.mTitle); 521 announceItemRemoved(suggestion.mTitle);
521 522
522 suggestionsSource.dismissSuggestion(suggestion); 523 suggestionsSource.dismissSuggestion(suggestion);
523 SuggestionsSection section = (SuggestionsSection) getGroup(position); 524 SuggestionsSection section = getSuggestionsSection(position);
524 section.removeSuggestion(suggestion); 525 section.removeSuggestion(suggestion);
525 } 526 }
526 527
527 private void dismissPromo() { 528 private void dismissPromo() {
528 // TODO(dgn): accessibility announcement. 529 // TODO(dgn): accessibility announcement.
529 mSigninPromo.dismiss(); 530 mSigninPromo.dismiss();
530 531
531 if (hasAllBeenDismissed()) { 532 if (hasAllBeenDismissed()) {
532 int footerPosition = getFooterPosition(); 533 int footerPosition = getFooterPosition();
533 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed); 534 mGroups.set(mGroups.indexOf(mFooter), mAllDismissed);
534 notifyItemChanged(footerPosition); 535 notifyItemChanged(footerPosition);
535 } 536 }
536 } 537 }
537 538
538 /** 539 /**
539 * Returns an unmodifiable list containing all items in the adapter.
540 */
541 private List<NewTabPageItem> getItems() {
542 List<NewTabPageItem> items = new ArrayList<>();
543 for (ItemGroup group : mGroups) {
544 items.addAll(group.getItems());
545 }
546 return Collections.unmodifiableList(items);
547 }
548
549 /**
550 * Returns another view holder that should be dismissed at the same time as the provided one. 540 * Returns another view holder that should be dismissed at the same time as the provided one.
551 */ 541 */
552 public ViewHolder getDismissSibling(ViewHolder viewHolder) { 542 public ViewHolder getDismissSibling(ViewHolder viewHolder) {
553 int swipePos = viewHolder.getAdapterPosition(); 543 int swipePos = viewHolder.getAdapterPosition();
554 ItemGroup group = getGroup(swipePos); 544 SuggestionsSection section = getSuggestionsSection(swipePos);
545 if (section == null) return null;
555 546
556 if (!(group instanceof SuggestionsSection)) return null; 547 int siblingPosDelta =
557 548 section.getDismissSiblingPosDelta(swipePos - getGroupPositionOff set(section));
558 SuggestionsSection section = (SuggestionsSection) group;
559 int siblingPosDelta = section.getDismissSiblingPosDelta(getItems().get(s wipePos));
560 if (siblingPosDelta == 0) return null; 549 if (siblingPosDelta == 0) return null;
561 550
562 return mRecyclerView.findViewHolderForAdapterPosition(siblingPosDelta + swipePos); 551 return mRecyclerView.findViewHolderForAdapterPosition(siblingPosDelta + swipePos);
563 } 552 }
564 553
565 private boolean hasAllBeenDismissed() { 554 private boolean hasAllBeenDismissed() {
566 return mSections.isEmpty() && !mSigninPromo.isShown(); 555 return mSections.isEmpty() && !mSigninPromo.isShown();
567 } 556 }
568 557
558 /**
559 * @param itemPosition The position of an item in the adapter.
560 * @return Returns the {@link SuggestionsSection} that contains the item at
561 * {@code itemPosition}, or null if the item is not part of one.
562 */
569 @VisibleForTesting 563 @VisibleForTesting
570 ItemGroup getGroup(int itemPosition) { 564 SuggestionsSection getSuggestionsSection(int itemPosition) {
571 int itemsSkipped = 0; 565 TreeNode child = mGroups.get(mRoot.getChildIndexForPosition(itemPosition ));
572 for (ItemGroup group : mGroups) { 566 if (!(child instanceof SuggestionsSection)) return null;
573 List<NewTabPageItem> items = group.getItems(); 567 return (SuggestionsSection) child;
574 itemsSkipped += items.size();
575 if (itemPosition < itemsSkipped) return group;
576 }
577 return null;
578 } 568 }
579 569
580 @VisibleForTesting 570 @VisibleForTesting
581 List<ItemGroup> getGroups() { 571 List<TreeNode> getGroups() {
582 return Collections.unmodifiableList(mGroups); 572 return Collections.unmodifiableList(mGroups);
583 } 573 }
584 574
585 @VisibleForTesting 575 @VisibleForTesting
586 int getGroupPositionOffset(ItemGroup group) { 576 int getGroupPositionOffset(TreeNode group) {
587 int positionOffset = 0; 577 return mRoot.getStartingOffsetForChild(group);
588 for (ItemGroup candidateGroup : mGroups) {
589 if (candidateGroup == group) return positionOffset;
590 positionOffset += candidateGroup.getItems().size();
591 }
592 Log.d(TAG, "Group not found: %s", group);
593 return RecyclerView.NO_POSITION;
594 } 578 }
595 579
596 @VisibleForTesting 580 @VisibleForTesting
597 SnippetArticle getSuggestionAt(int position) { 581 SnippetArticle getSuggestionAt(int position) {
598 return (SnippetArticle) getItems().get(position); 582 return mRoot.getSuggestionAt(position);
599 } 583 }
600 584
601 private void announceItemRemoved(String suggestionTitle) { 585 private void announceItemRemoved(String suggestionTitle) {
602 // In tests the RecyclerView can be null. 586 // In tests the RecyclerView can be null.
603 if (mRecyclerView == null) return; 587 if (mRecyclerView == null) return;
604 588
605 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS tring( 589 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS tring(
606 R.string.ntp_accessibility_item_removed, suggestionTitle)); 590 R.string.ntp_accessibility_item_removed, suggestionTitle));
607 } 591 }
608 } 592 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698