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.graphics.Canvas; | 7 import android.graphics.Canvas; |
8 import android.support.v4.view.ViewCompat; | 8 import android.support.v4.view.ViewCompat; |
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; |
(...skipping 14 matching lines...) Expand all Loading... |
25 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu
m; | 25 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnu
m; |
26 import org.chromium.chrome.browser.ntp.snippets.SectionHeader; | 26 import org.chromium.chrome.browser.ntp.snippets.SectionHeader; |
27 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; | 27 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; |
28 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; | 28 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; |
29 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; | 29 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; |
30 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; | 30 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; |
31 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; | 31 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; |
32 | 32 |
33 import java.util.ArrayList; | 33 import java.util.ArrayList; |
34 import java.util.Collections; | 34 import java.util.Collections; |
| 35 import java.util.LinkedHashMap; |
35 import java.util.List; | 36 import java.util.List; |
36 import java.util.Map; | 37 import java.util.Map; |
37 import java.util.TreeMap; | |
38 | 38 |
39 /** | 39 /** |
40 * A class that handles merging above the fold elements and below the fold cards
into an adapter | 40 * 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 | 41 * 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 | 42 * 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 | 43 * elements will be the cards shown to the user |
44 */ | 44 */ |
45 public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> | 45 public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> |
46 implements SuggestionsSource.Observer { | 46 implements SuggestionsSource.Observer { |
47 private static final String TAG = "Ntp"; | 47 private static final String TAG = "Ntp"; |
48 | 48 |
49 private final NewTabPageManager mNewTabPageManager; | 49 private final NewTabPageManager mNewTabPageManager; |
50 private final View mAboveTheFoldView; | 50 private final View mAboveTheFoldView; |
51 private SuggestionsSource mSuggestionsSource; | 51 private SuggestionsSource mSuggestionsSource; |
52 private final UiConfig mUiConfig; | 52 private final UiConfig mUiConfig; |
53 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback
s(); | 53 private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallback
s(); |
54 private NewTabPageRecyclerView mRecyclerView; | 54 private NewTabPageRecyclerView mRecyclerView; |
55 | 55 |
56 /** | 56 /** |
57 * List of all item groups (which can themselves contain multiple items. Whe
n flattened, this | 57 * List of all item groups (which can themselves contain multiple items. Whe
n flattened, this |
58 * will be a list of all items the adapter exposes. | 58 * will be a list of all items the adapter exposes. |
59 */ | 59 */ |
60 private final List<ItemGroup> mGroups = new ArrayList<>(); | 60 private final List<ItemGroup> mGroups = new ArrayList<>(); |
61 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); | 61 private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); |
62 private final SpacingItem mBottomSpacer = new SpacingItem(); | 62 private final SpacingItem mBottomSpacer = new SpacingItem(); |
63 | 63 |
64 /** Maps suggestion categories to sections, with stable iteration ordering.
*/ | 64 /** Maps suggestion categories to sections, with stable iteration ordering.
*/ |
65 private final Map<Integer, SuggestionsSection> mSections = new TreeMap<>(); | 65 private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap
<>(); |
66 | 66 |
67 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { | 67 private class ItemTouchCallbacks extends ItemTouchHelper.Callback { |
68 @Override | 68 @Override |
69 public void onSwiped(ViewHolder viewHolder, int direction) { | 69 public void onSwiped(ViewHolder viewHolder, int direction) { |
70 mRecyclerView.onItemDismissStarted(viewHolder.itemView); | 70 mRecyclerView.onItemDismissStarted(viewHolder.itemView); |
71 | 71 |
72 NewTabPageAdapter.this.dismissItem(viewHolder); | 72 NewTabPageAdapter.this.dismissItem(viewHolder); |
73 } | 73 } |
74 | 74 |
75 @Override | 75 @Override |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
138 for (int category : categories) { | 138 for (int category : categories) { |
139 int categoryStatus = suggestionsSource.getCategoryStatus(category); | 139 int categoryStatus = suggestionsSource.getCategoryStatus(category); |
140 assert categoryStatus != CategoryStatus.NOT_PROVIDED; | 140 assert categoryStatus != CategoryStatus.NOT_PROVIDED; |
141 if (categoryStatus == CategoryStatus.LOADING_ERROR | 141 if (categoryStatus == CategoryStatus.LOADING_ERROR |
142 || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISA
BLED) | 142 || categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISA
BLED) |
143 continue; | 143 continue; |
144 | 144 |
145 List<SnippetArticle> suggestions = | 145 List<SnippetArticle> suggestions = |
146 suggestionsSource.getSuggestionsForCategory(category); | 146 suggestionsSource.getSuggestionsForCategory(category); |
147 suggestionsPerCategory[i++] = suggestions.size(); | 147 suggestionsPerCategory[i++] = suggestions.size(); |
| 148 |
| 149 // Create the new section. |
| 150 SuggestionsCategoryInfo info = mSuggestionsSource.getCategoryInfo(ca
tegory); |
| 151 if (suggestions.isEmpty() && !info.showIfEmpty()) continue; |
| 152 mSections.put(category, new SuggestionsSection(category, info, this)
); |
| 153 |
| 154 // Add the new suggestions. |
148 setSuggestions(category, suggestions, categoryStatus); | 155 setSuggestions(category, suggestions, categoryStatus); |
149 } | 156 } |
150 // |mNewTabPageManager| is null in some tests. | 157 // |mNewTabPageManager| is null in some tests. |
151 if (mNewTabPageManager != null) { | 158 if (mNewTabPageManager != null) { |
152 mNewTabPageManager.trackSnippetsPageImpression(categories, suggestio
nsPerCategory); | 159 mNewTabPageManager.trackSnippetsPageImpression(categories, suggestio
nsPerCategory); |
153 } | 160 } |
154 suggestionsSource.setObserver(this); | 161 suggestionsSource.setObserver(this); |
155 updateGroups(); | 162 updateGroups(); |
156 } | 163 } |
157 | 164 |
158 /** Returns callbacks to configure the interactions with the RecyclerView's
items. */ | 165 /** Returns callbacks to configure the interactions with the RecyclerView's
items. */ |
159 public ItemTouchHelper.Callback getItemTouchCallbacks() { | 166 public ItemTouchHelper.Callback getItemTouchCallbacks() { |
160 return mItemTouchCallbacks; | 167 return mItemTouchCallbacks; |
161 } | 168 } |
162 | 169 |
163 @Override | 170 @Override |
164 public void onNewSuggestions(@CategoryInt int category) { | 171 public void onNewSuggestions(@CategoryInt int category) { |
| 172 // We never want to add suggestions from unknown categories. |
| 173 if (!mSections.containsKey(category)) return; |
| 174 |
165 // We never want to refresh the suggestions if we already have some cont
ent. | 175 // We never want to refresh the suggestions if we already have some cont
ent. |
166 if (mSections.containsKey(category) && mSections.get(category).hasSugges
tions()) return; | 176 if (mSections.get(category).hasSuggestions()) return; |
167 | 177 |
168 // The status may have changed while the suggestions were loading, perha
ps they should not | 178 // The status may have changed while the suggestions were loading, perha
ps they should not |
169 // be displayed any more. | 179 // be displayed any more. |
170 @CategoryStatusEnum | 180 @CategoryStatusEnum |
171 int status = mSuggestionsSource.getCategoryStatus(category); | 181 int status = mSuggestionsSource.getCategoryStatus(category); |
172 if (!SnippetsBridge.isCategoryEnabled(status)) { | 182 if (!SnippetsBridge.isCategoryEnabled(status)) { |
173 Log.w(TAG, "Received suggestions for a disabled category (id=%d, sta
tus=%d)", category, | 183 Log.w(TAG, "Received suggestions for a disabled category (id=%d, sta
tus=%d)", category, |
174 status); | 184 status); |
175 return; | 185 return; |
176 } | 186 } |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
309 // Count the number of suggestions before this category. | 319 // Count the number of suggestions before this category. |
310 int globalPositionOffset = 0; | 320 int globalPositionOffset = 0; |
311 for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()
) { | 321 for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()
) { |
312 if (entry.getKey() == category) break; | 322 if (entry.getKey() == category) break; |
313 globalPositionOffset += entry.getValue().getSuggestionsCount(); | 323 globalPositionOffset += entry.getValue().getSuggestionsCount(); |
314 } | 324 } |
315 // Assign global indices to the new suggestions. | 325 // Assign global indices to the new suggestions. |
316 for (SnippetArticle suggestion : suggestions) { | 326 for (SnippetArticle suggestion : suggestions) { |
317 suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosi
tion; | 327 suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosi
tion; |
318 } | 328 } |
319 // Add the new suggestions. | |
320 if (!mSections.containsKey(category)) { | |
321 SuggestionsCategoryInfo info = mSuggestionsSource.getCategoryInfo(ca
tegory); | |
322 if (suggestions.isEmpty() && !info.showIfEmpty()) return; | |
323 | |
324 mSections.put(category, new SuggestionsSection(category, info, this)
); | |
325 } | |
326 | 329 |
327 mSections.get(category).setSuggestions(suggestions, status); | 330 mSections.get(category).setSuggestions(suggestions, status); |
328 } | 331 } |
329 | 332 |
330 private void updateGroups() { | 333 private void updateGroups() { |
331 mGroups.clear(); | 334 mGroups.clear(); |
332 mGroups.add(mAboveTheFold); | 335 mGroups.add(mAboveTheFold); |
333 // TODO(treib,bauerb): Preserve the order of categories we got from getC
ategories. | 336 // TODO(treib,bauerb): Preserve the order of categories we got from getC
ategories. |
334 mGroups.addAll(mSections.values()); | 337 mGroups.addAll(mSections.values()); |
335 if (!mSections.isEmpty()) { | 338 if (!mSections.isEmpty()) { |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
401 ItemGroup getGroup(int itemPosition) { | 404 ItemGroup getGroup(int itemPosition) { |
402 int itemsSkipped = 0; | 405 int itemsSkipped = 0; |
403 for (ItemGroup group : mGroups) { | 406 for (ItemGroup group : mGroups) { |
404 List<NewTabPageItem> items = group.getItems(); | 407 List<NewTabPageItem> items = group.getItems(); |
405 itemsSkipped += items.size(); | 408 itemsSkipped += items.size(); |
406 if (itemPosition < itemsSkipped) return group; | 409 if (itemPosition < itemsSkipped) return group; |
407 } | 410 } |
408 return null; | 411 return null; |
409 } | 412 } |
410 | 413 |
| 414 @VisibleForTesting |
| 415 List<ItemGroup> getGroups() { |
| 416 return Collections.unmodifiableList(mGroups); |
| 417 } |
| 418 |
411 private int getGroupPositionOffset(ItemGroup group) { | 419 private int getGroupPositionOffset(ItemGroup group) { |
412 int positionOffset = 0; | 420 int positionOffset = 0; |
413 for (ItemGroup candidateGroup : mGroups) { | 421 for (ItemGroup candidateGroup : mGroups) { |
414 if (candidateGroup == group) return positionOffset; | 422 if (candidateGroup == group) return positionOffset; |
415 positionOffset += candidateGroup.getItems().size(); | 423 positionOffset += candidateGroup.getItems().size(); |
416 } | 424 } |
417 return RecyclerView.NO_POSITION; | 425 return RecyclerView.NO_POSITION; |
418 } | 426 } |
419 } | 427 } |
OLD | NEW |