OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |