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 |