// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.ntp.cards; import org.chromium.base.VisibleForTesting; import org.chromium.chrome.browser.ntp.snippets.CategoryInt; import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum; import org.chromium.chrome.browser.ntp.snippets.SectionHeader; import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A group of suggestions, with a header, a status card, and a progress indicator. */ public class SuggestionsSection implements ItemGroup { private final List<SnippetArticle> mSuggestions = new ArrayList<>(); private final SectionHeader mHeader; private final StatusItem mStatus; private final ProgressItem mProgressIndicator = new ProgressItem(); private final ActionItem mMoreButton; private final Observer mObserver; private final SuggestionsCategoryInfo mCategoryInfo; public SuggestionsSection(SuggestionsCategoryInfo info, Observer observer) { mHeader = new SectionHeader(info.getTitle()); mCategoryInfo = info; mObserver = observer; mMoreButton = new ActionItem(info); mStatus = StatusItem.createNoSuggestionsItem(info); } @Override public List<NewTabPageItem> getItems() { // Note: Keep this coherent with the various notify** calls on ItemGroup.Observer List<NewTabPageItem> items = new ArrayList<>(); items.add(mHeader); items.addAll(mSuggestions); if (mSuggestions.isEmpty()) items.add(mStatus); if (mCategoryInfo.hasMoreButton() || mSuggestions.isEmpty()) items.add(mMoreButton); if (mSuggestions.isEmpty()) items.add(mProgressIndicator); return Collections.unmodifiableList(items); } public void removeSuggestion(SnippetArticle suggestion) { int removedIndex = mSuggestions.indexOf(suggestion); if (removedIndex == -1) return; mSuggestions.remove(removedIndex); if (mMoreButton != null) mMoreButton.setDismissable(!hasSuggestions()); // Note: Keep this coherent with getItems() int globalRemovedIndex = removedIndex + 1; // Header has index 0 in the section. mObserver.notifyItemRemoved(this, globalRemovedIndex); // If we still have some suggestions, we are done. Otherwise, we'll have to notify about the // status-related items that are now present. if (hasSuggestions()) return; mObserver.notifyItemInserted(this, globalRemovedIndex); // Status card. if (!mCategoryInfo.hasMoreButton()) { mObserver.notifyItemInserted(this, globalRemovedIndex + 1); // Action card. } mObserver.notifyItemInserted(this, globalRemovedIndex + 2); // Progress indicator. } public void removeSuggestionById(String idWithinCategory) { for (SnippetArticle suggestion : mSuggestions) { if (suggestion.mIdWithinCategory.equals(idWithinCategory)) { removeSuggestion(suggestion); return; } } } public boolean hasSuggestions() { return !mSuggestions.isEmpty(); } public int getSuggestionsCount() { return mSuggestions.size(); } public void setSuggestions(List<SnippetArticle> suggestions, @CategoryStatusEnum int status) { copyThumbnails(suggestions); int itemCountBefore = getItems().size(); setStatusInternal(status); mSuggestions.clear(); mSuggestions.addAll(suggestions); if (mMoreButton != null) { mMoreButton.setPosition(mSuggestions.size()); mMoreButton.setDismissable(mSuggestions.isEmpty()); } mObserver.notifyGroupChanged(this, itemCountBefore, getItems().size()); } /** Sets the status for the section. Some statuses can cause the suggestions to be cleared. */ public void setStatus(@CategoryStatusEnum int status) { int itemCountBefore = getItems().size(); setStatusInternal(status); mObserver.notifyGroupChanged(this, itemCountBefore, getItems().size()); } private void setStatusInternal(@CategoryStatusEnum int status) { if (!SnippetsBridge.isCategoryStatusAvailable(status)) mSuggestions.clear(); mProgressIndicator.setVisible(SnippetsBridge.isCategoryLoading(status)); } @CategoryInt public int getCategory() { return mCategoryInfo.getCategory(); } private void copyThumbnails(List<SnippetArticle> suggestions) { for (SnippetArticle suggestion : suggestions) { int index = mSuggestions.indexOf(suggestion); if (index == -1) continue; suggestion.setThumbnailBitmap(mSuggestions.get(index).getThumbnailBitmap()); } } /** * The dismiss sibling is an item that should be dismissed at the same time as the provided * one. For example, if we want to dismiss a status card that has a More button attached, the * button is the card's dismiss sibling. This function return the adapter position delta to * apply to get to the sibling from the provided item. For the previous example, it would return * {@code +1}, as the button comes right after the status card. * * @return a position delta to apply to the position of the provided item to get the adapter * position of the item to animate. Returns {@code 0} if there is no dismiss sibling. */ public int getDismissSiblingPosDelta(NewTabPageItem item) { // The only dismiss siblings we have so far are the More button and the status card. // Exit early if there is no More button. if (mMoreButton == null) return 0; // When there are suggestions we won't have contiguous status and action items. if (hasSuggestions()) return 0; // The sibling of the more button is the status card, that should be right above. if (item == mMoreButton) return -1; // The sibling of the status card is the more button when it exists, should be right below. if (item == mStatus) return 1; return 0; } @VisibleForTesting ActionItem getActionItem() { return mMoreButton; } @VisibleForTesting StatusItem getStatusItem() { return mStatus; } }