| 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.annotation.StringRes; | 9 import android.support.annotation.StringRes; |
| 10 import android.support.v7.widget.RecyclerView; | 10 import android.support.v7.widget.RecyclerView; |
| 11 import android.support.v7.widget.RecyclerView.Adapter; | 11 import android.support.v7.widget.RecyclerView.Adapter; |
| 12 import android.support.v7.widget.RecyclerView.ViewHolder; | 12 import android.support.v7.widget.RecyclerView.ViewHolder; |
| 13 import android.support.v7.widget.helper.ItemTouchHelper; | 13 import android.support.v7.widget.helper.ItemTouchHelper; |
| 14 import android.view.View; | 14 import android.view.View; |
| 15 import android.view.ViewGroup; | 15 import android.view.ViewGroup; |
| 16 | 16 |
| 17 import org.chromium.base.Log; | 17 import org.chromium.base.Log; |
| 18 import org.chromium.base.VisibleForTesting; | 18 import org.chromium.base.VisibleForTesting; |
| 19 import org.chromium.chrome.R; | 19 import org.chromium.chrome.R; |
| 20 import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver; | |
| 21 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; | 20 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; |
| 22 import org.chromium.chrome.browser.ntp.UiConfig; | 21 import org.chromium.chrome.browser.ntp.UiConfig; |
| 23 import org.chromium.chrome.browser.ntp.snippets.CategoryInt; | |
| 24 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; | |
| 25 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu
m; | |
| 26 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; | 22 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; |
| 27 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; | 23 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; |
| 28 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; | 24 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; |
| 29 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; | |
| 30 import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig; | |
| 31 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; | 25 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; |
| 32 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; | 26 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 33 | 27 |
| 34 import java.util.ArrayList; | 28 import java.util.Arrays; |
| 35 import java.util.LinkedHashMap; | |
| 36 import java.util.List; | 29 import java.util.List; |
| 37 import java.util.Map; | |
| 38 | 30 |
| 39 /** | 31 /** |
| 40 * A class that handles merging above the fold elements and below the fold cards
into an adapter | 32 * A class that handles merging above the fold elements and below the fold cards
into an adapter |
| 41 * that will be used to back the NTP RecyclerView. The first element in the adap
ter should always be | 33 * that will be used to back the NTP RecyclerView. The first element in the adap
ter should always be |
| 42 * the above-the-fold view (containing the logo, search box, and most visited ti
les) and subsequent | 34 * the above-the-fold view (containing the logo, search box, and most visited ti
les) and subsequent |
| 43 * elements will be the cards shown to the user | 35 * elements will be the cards shown to the user |
| 44 */ | 36 */ |
| 45 public class NewTabPageAdapter | 37 public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements
NodeParent { |
| 46 extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Obser
ver, NodeParent { | |
| 47 private static final String TAG = "Ntp"; | 38 private static final String TAG = "Ntp"; |
| 48 | 39 |
| 49 private final NewTabPageManager mNewTabPageManager; | 40 private final NewTabPageManager mNewTabPageManager; |
| 50 private final View mAboveTheFoldView; | 41 private final View mAboveTheFoldView; |
| 51 private final UiConfig mUiConfig; | 42 private final UiConfig mUiConfig; |
| 52 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback
s(); | 43 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback
s(); |
| 53 private final OfflinePageBridge mOfflinePageBridge; | |
| 54 private NewTabPageRecyclerView mRecyclerView; | 44 private NewTabPageRecyclerView mRecyclerView; |
| 55 | 45 |
| 56 /** | 46 /** |
| 57 * List of all child nodes (which can themselves contain multiple child node
s). | 47 * List of all child nodes (which can themselves contain multiple child node
s). |
| 58 */ | 48 */ |
| 59 private final List<TreeNode> mChildren = new ArrayList<>(); | 49 private final List<TreeNode> mChildren; |
| 50 private final InnerNode mRoot; |
| 51 |
| 60 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); | 52 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); |
| 53 private final SectionList mSections; |
| 61 private final SignInPromo mSigninPromo; | 54 private final SignInPromo mSigninPromo; |
| 62 private final AllDismissedItem mAllDismissed; | 55 private final AllDismissedItem mAllDismissed; |
| 63 private final Footer mFooter; | 56 private final Footer mFooter; |
| 64 private final SpacingItem mBottomSpacer = new SpacingItem(); | 57 private final SpacingItem mBottomSpacer = new SpacingItem(); |
| 65 private final InnerNode mRoot; | |
| 66 | |
| 67 /** Maps suggestion categories to sections, with stable iteration ordering.
*/ | |
| 68 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap
<>(); | |
| 69 | 58 |
| 70 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { | 59 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { |
| 71 @Override | 60 @Override |
| 72 public void onSwiped(ViewHolder viewHolder, int direction) { | 61 public void onSwiped(ViewHolder viewHolder, int direction) { |
| 73 mRecyclerView.onItemDismissStarted(viewHolder); | 62 mRecyclerView.onItemDismissStarted(viewHolder); |
| 74 NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition()); | 63 NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition()); |
| 75 } | 64 } |
| 76 | 65 |
| 77 @Override | 66 @Override |
| 78 public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
{ | 67 public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
{ |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 136 * @param uiConfig the NTP UI configuration, to be passed to created views. | 125 * @param uiConfig the NTP UI configuration, to be passed to created views. |
| 137 * @param offlinePageBridge the OfflinePageBridge used to determine if artic
les are available | 126 * @param offlinePageBridge the OfflinePageBridge used to determine if artic
les are available |
| 138 * offline. | 127 * offline. |
| 139 * | 128 * |
| 140 */ | 129 */ |
| 141 public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, U
iConfig uiConfig, | 130 public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, U
iConfig uiConfig, |
| 142 OfflinePageBridge offlinePageBridge) { | 131 OfflinePageBridge offlinePageBridge) { |
| 143 mNewTabPageManager = manager; | 132 mNewTabPageManager = manager; |
| 144 mAboveTheFoldView = aboveTheFoldView; | 133 mAboveTheFoldView = aboveTheFoldView; |
| 145 mUiConfig = uiConfig; | 134 mUiConfig = uiConfig; |
| 146 mOfflinePageBridge = offlinePageBridge; | |
| 147 mRoot = new InnerNode(this) { | 135 mRoot = new InnerNode(this) { |
| 148 @Override | 136 @Override |
| 149 protected List<TreeNode> getChildren() { | 137 protected List<TreeNode> getChildren() { |
| 150 return mChildren; | 138 return mChildren; |
| 151 } | 139 } |
| 152 | |
| 153 @Override | |
| 154 public void onItemRangeChanged(TreeNode child, int index, int count)
{ | |
| 155 if (mChildren.isEmpty()) return; // The sections have not been i
nitialised yet. | |
| 156 super.onItemRangeChanged(child, index, count); | |
| 157 } | |
| 158 | |
| 159 @Override | |
| 160 public void onItemRangeInserted(TreeNode child, int index, int count
) { | |
| 161 if (mChildren.isEmpty()) return; // The sections have not been i
nitialised yet. | |
| 162 super.onItemRangeInserted(child, index, count); | |
| 163 } | |
| 164 | |
| 165 @Override | |
| 166 public void onItemRangeRemoved(TreeNode child, int index, int count)
{ | |
| 167 if (mChildren.isEmpty()) return; // The sections have not been i
nitialised yet. | |
| 168 super.onItemRangeRemoved(child, index, count); | |
| 169 } | |
| 170 }; | 140 }; |
| 171 | 141 |
| 172 mSigninPromo = new SignInPromo(mRoot); | 142 mSections = new SectionList(mRoot, mNewTabPageManager, offlinePageBridge
); |
| 143 mSigninPromo = new SignInPromo(mRoot, mNewTabPageManager); |
| 173 mAllDismissed = new AllDismissedItem(mRoot); | 144 mAllDismissed = new AllDismissedItem(mRoot); |
| 174 mFooter = new Footer(mRoot); | 145 mFooter = new Footer(mRoot); |
| 175 DestructionObserver signInObserver = mSigninPromo.getObserver(); | |
| 176 if (signInObserver != null) mNewTabPageManager.addDestructionObserver(si
gnInObserver); | |
| 177 | 146 |
| 178 resetSections(/*alwaysAllowEmptySections=*/false); | 147 mChildren = Arrays.asList( |
| 179 mNewTabPageManager.getSuggestionsSource().setObserver(this); | 148 mAboveTheFold, mSections, mSigninPromo, mAllDismissed, mFooter,
mBottomSpacer); |
| 180 } | 149 mRoot.init(); |
| 181 | 150 |
| 182 /** | 151 updateAllDismissedVisibility(); |
| 183 * Resets the sections, reloading the whole new tab page content. | |
| 184 * @param alwaysAllowEmptySections Whether sections are always allowed to be
displayed when | |
| 185 * they are empty, even when they are normally not. | |
| 186 */ | |
| 187 public void resetSections(boolean alwaysAllowEmptySections) { | |
| 188 mSections.clear(); | |
| 189 mChildren.clear(); | |
| 190 | |
| 191 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS
ource(); | |
| 192 int[] categories = suggestionsSource.getCategories(); | |
| 193 int[] suggestionsPerCategory = new int[categories.length]; | |
| 194 int i = 0; | |
| 195 for (int category : categories) { | |
| 196 int categoryStatus = suggestionsSource.getCategoryStatus(category); | |
| 197 if (categoryStatus == CategoryStatus.LOADING_ERROR | |
| 198 || categoryStatus == CategoryStatus.NOT_PROVIDED | |
| 199 || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISA
BLED) | |
| 200 continue; | |
| 201 | |
| 202 suggestionsPerCategory[i++] = | |
| 203 resetSection(category, categoryStatus, alwaysAllowEmptySecti
ons); | |
| 204 } | |
| 205 | |
| 206 mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPe
rCategory); | |
| 207 | |
| 208 updateChildren(); | |
| 209 } | |
| 210 | |
| 211 /** | |
| 212 * Resets the section for {@code category}. Removes the section if there are
no suggestions for | |
| 213 * it and it is not allowed to be empty. Otherwise, creates the section if i
t is not present | |
| 214 * yet. Sets the available suggestions on the section. | |
| 215 * @param category The category for which the section must be reset. | |
| 216 * @param categoryStatus The category status. | |
| 217 * @param alwaysAllowEmptySections Whether sections are always allowed to be
displayed when | |
| 218 * they are empty, even when they are normally not. | |
| 219 * @return The number of suggestions for the section. | |
| 220 */ | |
| 221 private int resetSection(@CategoryInt int category, @CategoryStatusEnum int
categoryStatus, | |
| 222 boolean alwaysAllowEmptySections) { | |
| 223 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS
ource(); | |
| 224 List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCa
tegory(category); | |
| 225 SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(categor
y); | |
| 226 | |
| 227 // Do not show an empty section if not allowed. | |
| 228 if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySec
tions) { | |
| 229 mSections.remove(category); | |
| 230 return 0; | |
| 231 } | |
| 232 | |
| 233 // Create the section if needed. | |
| 234 SuggestionsSection section = mSections.get(category); | |
| 235 if (section == null) { | |
| 236 section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mO
fflinePageBridge); | |
| 237 mSections.put(category, section); | |
| 238 } | |
| 239 | |
| 240 // Add the new suggestions. | |
| 241 setSuggestions(category, suggestions, categoryStatus); | |
| 242 | |
| 243 return suggestions.size(); | |
| 244 } | 152 } |
| 245 | 153 |
| 246 /** Returns callbacks to configure the interactions with the RecyclerView's
items. */ | 154 /** Returns callbacks to configure the interactions with the RecyclerView's
items. */ |
| 247 public ItemTouchHelper.Callback getItemTouchCallbacks() { | 155 public ItemTouchHelper.Callback getItemTouchCallbacks() { |
| 248 return mItemTouchCallbacks; | 156 return mItemTouchCallbacks; |
| 249 } | 157 } |
| 250 | 158 |
| 251 @Override | 159 @Override |
| 252 public void onNewSuggestions(@CategoryInt int category) { | |
| 253 @CategoryStatusEnum | |
| 254 int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus
(category); | |
| 255 | |
| 256 if (!canLoadSuggestions(category, status)) return; | |
| 257 | |
| 258 // We never want to refresh the suggestions if we already have some cont
ent. | |
| 259 if (mSections.get(category).hasSuggestions()) return; | |
| 260 | |
| 261 List<SnippetArticle> suggestions = | |
| 262 mNewTabPageManager.getSuggestionsSource().getSuggestionsForCateg
ory(category); | |
| 263 | |
| 264 Log.d(TAG, "Received %d new suggestions for category %d.", suggestions.s
ize(), category); | |
| 265 | |
| 266 // At first, there might be no suggestions available, we wait until they
have been fetched. | |
| 267 if (suggestions.isEmpty()) return; | |
| 268 | |
| 269 setSuggestions(category, suggestions, status); | |
| 270 } | |
| 271 | |
| 272 @Override | |
| 273 public void onMoreSuggestions(@CategoryInt int category, List<SnippetArticle
> suggestions) { | |
| 274 @CategoryStatusEnum | |
| 275 int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus
(category); | |
| 276 if (!canLoadSuggestions(category, status)) return; | |
| 277 | |
| 278 setSuggestions(category, suggestions, status); | |
| 279 } | |
| 280 | |
| 281 @Override | |
| 282 public void onCategoryStatusChanged(@CategoryInt int category, @CategoryStat
usEnum int status) { | |
| 283 // Observers should not be registered for this state. | |
| 284 assert status != CategoryStatus.ALL_SUGGESTIONS_EXPLICITLY_DISABLED; | |
| 285 | |
| 286 // If there is no section for this category there is nothing to do. | |
| 287 if (!mSections.containsKey(category)) return; | |
| 288 | |
| 289 switch (status) { | |
| 290 case CategoryStatus.NOT_PROVIDED: | |
| 291 // The section provider has gone away. Keep open UIs as they are
. | |
| 292 return; | |
| 293 | |
| 294 case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED: | |
| 295 case CategoryStatus.LOADING_ERROR: | |
| 296 // Need to remove the entire section from the UI immediately. | |
| 297 removeSection(mSections.get(category)); | |
| 298 return; | |
| 299 | |
| 300 case CategoryStatus.SIGNED_OUT: | |
| 301 // TODO(dgn): We currently can only reach this through an old va
riation parameter. | |
| 302 default: | |
| 303 mSections.get(category).setStatus(status); | |
| 304 return; | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 @Override | |
| 309 public void onSuggestionInvalidated(@CategoryInt int category, String idWith
inCategory) { | |
| 310 if (!mSections.containsKey(category)) return; | |
| 311 mSections.get(category).removeSuggestionById(idWithinCategory); | |
| 312 } | |
| 313 | |
| 314 @Override | |
| 315 public void onFullRefreshRequired() { | |
| 316 resetSections(/*alwaysAllowEmptySections=*/false); | |
| 317 } | |
| 318 | |
| 319 @Override | |
| 320 @ItemViewType | 160 @ItemViewType |
| 321 public int getItemViewType(int position) { | 161 public int getItemViewType(int position) { |
| 322 return mRoot.getItemViewType(position); | 162 return mRoot.getItemViewType(position); |
| 323 } | 163 } |
| 324 | 164 |
| 325 @Override | 165 @Override |
| 326 public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp
e) { | 166 public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp
e) { |
| 327 assert parent == mRecyclerView; | 167 assert parent == mRecyclerView; |
| 328 | 168 |
| 329 switch (viewType) { | 169 switch (viewType) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 348 case ItemViewType.ACTION: | 188 case ItemViewType.ACTION: |
| 349 return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManag
er, mUiConfig); | 189 return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManag
er, mUiConfig); |
| 350 | 190 |
| 351 case ItemViewType.PROMO: | 191 case ItemViewType.PROMO: |
| 352 return new SignInPromo.ViewHolder(mRecyclerView, mNewTabPageMana
ger, mUiConfig); | 192 return new SignInPromo.ViewHolder(mRecyclerView, mNewTabPageMana
ger, mUiConfig); |
| 353 | 193 |
| 354 case ItemViewType.FOOTER: | 194 case ItemViewType.FOOTER: |
| 355 return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager); | 195 return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager); |
| 356 | 196 |
| 357 case ItemViewType.ALL_DISMISSED: | 197 case ItemViewType.ALL_DISMISSED: |
| 358 return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPag
eManager, this); | 198 return new AllDismissedItem.ViewHolder(mRecyclerView, mSections)
; |
| 359 } | 199 } |
| 360 | 200 |
| 361 assert false : viewType; | 201 assert false : viewType; |
| 362 return null; | 202 return null; |
| 363 } | 203 } |
| 364 | 204 |
| 365 @Override | 205 @Override |
| 366 public void onBindViewHolder(NewTabPageViewHolder holder, final int position
) { | 206 public void onBindViewHolder(NewTabPageViewHolder holder, final int position
) { |
| 367 mRoot.onBindViewHolder(holder, position); | 207 mRoot.onBindViewHolder(holder, position); |
| 368 } | 208 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 380 return getFirstPositionForType(ItemViewType.HEADER); | 220 return getFirstPositionForType(ItemViewType.HEADER); |
| 381 } | 221 } |
| 382 | 222 |
| 383 public int getFirstCardPosition() { | 223 public int getFirstCardPosition() { |
| 384 for (int i = 0; i < getItemCount(); ++i) { | 224 for (int i = 0; i < getItemCount(); ++i) { |
| 385 if (CardViewHolder.isCard(getItemViewType(i))) return i; | 225 if (CardViewHolder.isCard(getItemViewType(i))) return i; |
| 386 } | 226 } |
| 387 return RecyclerView.NO_POSITION; | 227 return RecyclerView.NO_POSITION; |
| 388 } | 228 } |
| 389 | 229 |
| 230 int getLastContentItemPosition() { |
| 231 return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mF
ooter); |
| 232 } |
| 233 |
| 390 int getBottomSpacerPosition() { | 234 int getBottomSpacerPosition() { |
| 391 return getChildPositionOffset(mBottomSpacer); | 235 return getChildPositionOffset(mBottomSpacer); |
| 392 } | 236 } |
| 393 | 237 |
| 394 int getLastContentItemPosition() { | |
| 395 return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mF
ooter); | |
| 396 } | |
| 397 | |
| 398 private void setSuggestions(@CategoryInt int category, List<SnippetArticle>
suggestions, | |
| 399 @CategoryStatusEnum int status) { | |
| 400 // Count the number of suggestions before this category. | |
| 401 int globalPositionOffset = 0; | |
| 402 for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()
) { | |
| 403 if (entry.getKey() == category) break; | |
| 404 globalPositionOffset += entry.getValue().getSuggestionsCount(); | |
| 405 } | |
| 406 // Assign global indices to the new suggestions. | |
| 407 for (SnippetArticle suggestion : suggestions) { | |
| 408 suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosi
tion; | |
| 409 } | |
| 410 | |
| 411 mSections.get(category).addSuggestions(suggestions, status); | |
| 412 } | |
| 413 | |
| 414 private void updateChildren() { | |
| 415 mChildren.clear(); | |
| 416 mChildren.add(mAboveTheFold); | |
| 417 mChildren.addAll(mSections.values()); | |
| 418 mChildren.add(mSigninPromo); | |
| 419 mChildren.add(mAllDismissed); | |
| 420 mChildren.add(mFooter); | |
| 421 mChildren.add(mBottomSpacer); | |
| 422 | |
| 423 updateAllDismissedVisibility(); | |
| 424 | |
| 425 // TODO(mvanouwerkerk): Notify about the subset of changed items. At lea
st |mAboveTheFold| | |
| 426 // has not changed when refreshing from the all dismissed state. | |
| 427 notifyDataSetChanged(); | |
| 428 } | |
| 429 | |
| 430 private void updateAllDismissedVisibility() { | 238 private void updateAllDismissedVisibility() { |
| 431 boolean showAllDismissed = hasAllBeenDismissed(); | 239 boolean showAllDismissed = hasAllBeenDismissed(); |
| 432 mAllDismissed.setVisible(showAllDismissed); | 240 mAllDismissed.setVisible(showAllDismissed); |
| 433 mFooter.setVisible(!showAllDismissed); | 241 mFooter.setVisible(!showAllDismissed); |
| 434 } | 242 } |
| 435 | 243 |
| 436 private void removeSection(SuggestionsSection section) { | |
| 437 mSections.remove(section.getCategory()); | |
| 438 int startPos = getChildPositionOffset(section); | |
| 439 mChildren.remove(section); | |
| 440 notifyItemRangeRemoved(startPos, section.getItemCount()); | |
| 441 | |
| 442 updateAllDismissedVisibility(); | |
| 443 | |
| 444 notifyItemChanged(getBottomSpacerPosition()); | |
| 445 } | |
| 446 | |
| 447 @Override | 244 @Override |
| 448 public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCou
nt) { | 245 public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCou
nt) { |
| 449 assert child == mRoot; | 246 assert child == mRoot; |
| 450 notifyItemRangeChanged(itemPosition, itemCount); | 247 notifyItemRangeChanged(itemPosition, itemCount); |
| 451 } | 248 } |
| 452 | 249 |
| 453 @Override | 250 @Override |
| 454 public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCo
unt) { | 251 public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCo
unt) { |
| 455 assert child == mRoot; | 252 assert child == mRoot; |
| 456 notifyItemRangeInserted(itemPosition, itemCount); | 253 notifyItemRangeInserted(itemPosition, itemCount); |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 488 */ | 285 */ |
| 489 // TODO(crbug.com/635567): Fix this properly. | 286 // TODO(crbug.com/635567): Fix this properly. |
| 490 @SuppressLint("SwitchIntDef") | 287 @SuppressLint("SwitchIntDef") |
| 491 public void dismissItem(int position) { | 288 public void dismissItem(int position) { |
| 492 int itemViewType = getItemViewType(position); | 289 int itemViewType = getItemViewType(position); |
| 493 | 290 |
| 494 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st
uff. | 291 // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of st
uff. |
| 495 switch (itemViewType) { | 292 switch (itemViewType) { |
| 496 case ItemViewType.STATUS: | 293 case ItemViewType.STATUS: |
| 497 case ItemViewType.ACTION: | 294 case ItemViewType.ACTION: |
| 498 dismissSection(getSuggestionsSection(position)); | 295 dismissSection(position); |
| 499 return; | 296 return; |
| 500 | 297 |
| 501 case ItemViewType.SNIPPET: | 298 case ItemViewType.SNIPPET: |
| 502 dismissSuggestion(position); | 299 dismissSuggestion(position); |
| 503 return; | 300 return; |
| 504 | 301 |
| 505 case ItemViewType.PROMO: | 302 case ItemViewType.PROMO: |
| 506 dismissPromo(); | 303 dismissPromo(); |
| 507 return; | 304 return; |
| 508 | 305 |
| 509 default: | 306 default: |
| 510 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie
wType); | 307 Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemVie
wType); |
| 511 return; | 308 return; |
| 512 } | 309 } |
| 513 } | 310 } |
| 514 | 311 |
| 515 private void dismissSection(SuggestionsSection section) { | 312 private void dismissSection(int position) { |
| 516 assert SnippetsConfig.isSectionDismissalEnabled(); | 313 SuggestionsSection section = getSuggestionsSection(position); |
| 517 | 314 mSections.dismissSection(section); |
| 518 announceItemRemoved(section.getHeaderText()); | 315 announceItemRemoved(section.getHeaderText()); |
| 519 | |
| 520 mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCat
egory()); | |
| 521 removeSection(section); | |
| 522 } | 316 } |
| 523 | 317 |
| 524 private void dismissSuggestion(int position) { | 318 private void dismissSuggestion(int position) { |
| 525 SnippetArticle suggestion = mRoot.getSuggestionAt(position); | 319 SnippetArticle suggestion = mRoot.getSuggestionAt(position); |
| 526 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS
ource(); | 320 SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsS
ource(); |
| 527 if (suggestionsSource == null) { | 321 if (suggestionsSource == null) { |
| 528 // It is possible for this method to be called after the NewTabPage
has had destroy() | 322 // It is possible for this method to be called after the NewTabPage
has had destroy() |
| 529 // called. This can happen when NewTabPageRecyclerView.dismissWithAn
imation() is called | 323 // called. This can happen when NewTabPageRecyclerView.dismissWithAn
imation() is called |
| 530 // and the animation ends after the user has navigated away. In this
case we cannot | 324 // and the animation ends after the user has navigated away. In this
case we cannot |
| 531 // inform the native side that the snippet has been dismissed (http:
//crbug.com/649299). | 325 // inform the native side that the snippet has been dismissed (http:
//crbug.com/649299). |
| (...skipping 21 matching lines...) Expand all Loading... |
| 553 if (siblingPosDelta == 0) return null; | 347 if (siblingPosDelta == 0) return null; |
| 554 | 348 |
| 555 return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosi
tion( | 349 return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosi
tion( |
| 556 siblingPosDelta + swipePos); | 350 siblingPosDelta + swipePos); |
| 557 } | 351 } |
| 558 | 352 |
| 559 private boolean hasAllBeenDismissed() { | 353 private boolean hasAllBeenDismissed() { |
| 560 return mSections.isEmpty() && !mSigninPromo.isVisible(); | 354 return mSections.isEmpty() && !mSigninPromo.isVisible(); |
| 561 } | 355 } |
| 562 | 356 |
| 563 private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatu
sEnum int status) { | |
| 564 // We never want to add suggestions from unknown categories. | |
| 565 if (!mSections.containsKey(category)) return false; | |
| 566 | |
| 567 // The status may have changed while the suggestions were loading, perha
ps they should not | |
| 568 // be displayed any more. | |
| 569 if (!SnippetsBridge.isCategoryEnabled(status)) { | |
| 570 Log.w(TAG, "Received suggestions for a disabled category (id=%d, sta
tus=%d)", category, | |
| 571 status); | |
| 572 return false; | |
| 573 } | |
| 574 | |
| 575 return true; | |
| 576 } | |
| 577 | |
| 578 /** | 357 /** |
| 579 * @param itemPosition The position of an item in the adapter. | 358 * @param itemPosition The position of an item in the adapter. |
| 580 * @return Returns the {@link SuggestionsSection} that contains the item at | 359 * @return Returns the {@link SuggestionsSection} that contains the item at |
| 581 * {@code itemPosition}, or null if the item is not part of one. | 360 * {@code itemPosition}, or null if the item is not part of one. |
| 582 */ | 361 */ |
| 583 private SuggestionsSection getSuggestionsSection(int itemPosition) { | 362 private SuggestionsSection getSuggestionsSection(int itemPosition) { |
| 584 TreeNode child = mRoot.getChildForPosition(itemPosition); | 363 int relativePosition = itemPosition - mRoot.getStartingOffsetForChild(mS
ections); |
| 364 assert relativePosition >= 0; |
| 365 TreeNode child = mSections.getChildForPosition(relativePosition); |
| 585 if (!(child instanceof SuggestionsSection)) return null; | 366 if (!(child instanceof SuggestionsSection)) return null; |
| 586 return (SuggestionsSection) child; | 367 return (SuggestionsSection) child; |
| 587 } | 368 } |
| 588 | 369 |
| 589 private int getChildPositionOffset(TreeNode child) { | 370 private int getChildPositionOffset(TreeNode child) { |
| 590 return mRoot.getStartingOffsetForChild(child); | 371 return mRoot.getStartingOffsetForChild(child); |
| 591 } | 372 } |
| 592 | 373 |
| 593 @VisibleForTesting | 374 @VisibleForTesting |
| 594 SnippetArticle getSuggestionAt(int position) { | 375 SnippetArticle getSuggestionAt(int position) { |
| 595 return mRoot.getSuggestionAt(position); | 376 return mRoot.getSuggestionAt(position); |
| 596 } | 377 } |
| 597 | 378 |
| 598 @VisibleForTesting | 379 @VisibleForTesting |
| 599 int getFirstPositionForType(@ItemViewType int viewType) { | 380 int getFirstPositionForType(@ItemViewType int viewType) { |
| 600 int count = getItemCount(); | 381 int count = getItemCount(); |
| 601 for (int i = 0; i < count; i++) { | 382 for (int i = 0; i < count; i++) { |
| 602 if (getItemViewType(i) == viewType) return i; | 383 if (getItemViewType(i) == viewType) return i; |
| 603 } | 384 } |
| 604 return RecyclerView.NO_POSITION; | 385 return RecyclerView.NO_POSITION; |
| 605 } | 386 } |
| 606 | 387 |
| 607 SuggestionsSection getSectionForTesting(@CategoryInt int category) { | 388 SectionList getSectionListForTesting() { |
| 608 return mSections.get(category); | 389 return mSections; |
| 609 } | 390 } |
| 610 | 391 |
| 611 InnerNode getRootForTesting() { | 392 InnerNode getRootForTesting() { |
| 612 return mRoot; | 393 return mRoot; |
| 613 } | 394 } |
| 614 | 395 |
| 615 private void announceItemRemoved(String itemTitle) { | 396 private void announceItemRemoved(String itemTitle) { |
| 616 // In tests the RecyclerView can be null. | 397 // In tests the RecyclerView can be null. |
| 617 if (mRecyclerView == null) return; | 398 if (mRecyclerView == null) return; |
| 618 | 399 |
| 619 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS
tring( | 400 mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getS
tring( |
| 620 R.string.ntp_accessibility_item_removed, itemTitle)); | 401 R.string.ntp_accessibility_item_removed, itemTitle)); |
| 621 } | 402 } |
| 622 | 403 |
| 623 private void announceItemRemoved(@StringRes int stringToAnnounce) { | 404 private void announceItemRemoved(@StringRes int stringToAnnounce) { |
| 624 // In tests the RecyclerView can be null. | 405 // In tests the RecyclerView can be null. |
| 625 if (mRecyclerView == null) return; | 406 if (mRecyclerView == null) return; |
| 626 | 407 |
| 627 announceItemRemoved(mRecyclerView.getResources().getString(stringToAnnou
nce)); | 408 announceItemRemoved(mRecyclerView.getResources().getString(stringToAnnou
nce)); |
| 628 } | 409 } |
| 629 } | 410 } |
| OLD | NEW |