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