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