// Copyright 2015 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.bookmarks; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.chromium.base.annotations.SuppressFBWarnings; import org.chromium.chrome.R; import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem; import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver; import org.chromium.chrome.browser.bookmarks.BookmarkPromoHeader.PromoHeaderShowingChangeListener; import org.chromium.components.bookmarks.BookmarkId; import java.util.ArrayList; import java.util.List; /** * BaseAdapter for {@link BookmarkRecyclerView}. It manages bookmarks to list there. */ class BookmarkItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements BookmarkUIObserver, PromoHeaderShowingChangeListener { private static final int PROMO_HEADER_VIEW = 0; private static final int FOLDER_VIEW = 1; private static final int DIVIDER_VIEW = 2; private static final int BOOKMARK_VIEW = 3; private BookmarkDelegate mDelegate; private Context mContext; private BookmarkPromoHeader mPromoHeaderManager; private List<List<? extends Object>> mSections; private List<Object> mPromoHeaderSection = new ArrayList<>(); private List<Object> mFolderDividerSection = new ArrayList<>(); private List<BookmarkId> mFolderSection = new ArrayList<>(); private List<Object> mBookmarkDividerSection = new ArrayList<>(); private List<BookmarkId> mBookmarkSection = new ArrayList<>(); private BookmarkModelObserver mBookmarkModelObserver = new BookmarkModelObserver() { @Override public void bookmarkNodeChanged(BookmarkItem node) { assert mDelegate != null; int position = getPositionForBookmark(node.getId()); if (position >= 0) notifyItemChanged(position); } @Override public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node, boolean isDoingExtensiveChanges) { assert mDelegate != null; if (node.isFolder()) { mDelegate.notifyStateChange(BookmarkItemsAdapter.this); } else { int deletedPosition = getPositionForBookmark(node.getId()); if (deletedPosition >= 0) { removeItem(deletedPosition); } } } @Override public void bookmarkModelChanged() { assert mDelegate != null; mDelegate.notifyStateChange(BookmarkItemsAdapter.this); } }; BookmarkItemsAdapter(Context context) { mContext = context; mSections = new ArrayList<>(); mSections.add(mPromoHeaderSection); mSections.add(mFolderDividerSection); mSections.add(mFolderSection); mSections.add(mBookmarkDividerSection); mSections.add(mBookmarkSection); } BookmarkId getItem(int position) { return (BookmarkId) getSection(position).get(toSectionPosition(position)); } private int toSectionPosition(int globalPosition) { int sectionPosition = globalPosition; for (List<?> section : mSections) { if (sectionPosition < section.size()) break; sectionPosition -= section.size(); } return sectionPosition; } private List<? extends Object> getSection(int position) { int i = position; for (List<? extends Object> section : mSections) { if (i < section.size()) { return section; } i -= section.size(); } return null; } /** * @return The position of the given bookmark in adapter. Will return -1 if not found. */ private int getPositionForBookmark(BookmarkId bookmark) { assert bookmark != null; int position = -1; for (int i = 0; i < getItemCount(); i++) { if (bookmark.equals(getItem(i))) { position = i; break; } } return position; } /** * Set folders and bookmarks to show. * @param folders This can be null if there is no folders to show. */ private void setBookmarks(List<BookmarkId> folders, List<BookmarkId> bookmarks) { if (folders == null) folders = new ArrayList<BookmarkId>(); mFolderSection.clear(); mFolderSection.addAll(folders); mBookmarkSection.clear(); mBookmarkSection.addAll(bookmarks); updateHeader(); updateDividerSections(); // TODO(kkimlabs): Animation is disabled due to a performance issue on bookmark undo. // http://crbug.com/484174 notifyDataSetChanged(); } private void updateDividerSections() { mFolderDividerSection.clear(); mBookmarkDividerSection.clear(); boolean isHeaderPresent = !mPromoHeaderSection.isEmpty(); if (isHeaderPresent && !mFolderSection.isEmpty()) { mFolderDividerSection.add(null); } if ((isHeaderPresent || !mFolderSection.isEmpty()) && !mBookmarkSection.isEmpty()) { mBookmarkDividerSection.add(null); } } private void removeItem(int position) { List<?> section = getSection(position); assert section == mFolderSection || section == mBookmarkSection; section.remove(toSectionPosition(position)); notifyItemRemoved(position); } // RecyclerView.Adapter implementation. @Override public int getItemCount() { int count = 0; for (List<?> section : mSections) { count += section.size(); } return count; } @Override public int getItemViewType(int position) { List<?> section = getSection(position); if (section == mPromoHeaderSection) { return PROMO_HEADER_VIEW; } else if (section == mFolderDividerSection || section == mBookmarkDividerSection) { return DIVIDER_VIEW; } else if (section == mFolderSection) { return FOLDER_VIEW; } else if (section == mBookmarkSection) { return BOOKMARK_VIEW; } assert false : "Invalid position requested"; return -1; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { assert mDelegate != null; switch (viewType) { case PROMO_HEADER_VIEW: return mPromoHeaderManager.createHolder(parent); case DIVIDER_VIEW: return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( R.layout.bookmark_divider, parent, false)) {}; case FOLDER_VIEW: BookmarkFolderRow folder = (BookmarkFolderRow) LayoutInflater.from( parent.getContext()).inflate(R.layout.bookmark_folder_row, parent, false); folder.onBookmarkDelegateInitialized(mDelegate); return new ItemViewHolder(folder); case BOOKMARK_VIEW: BookmarkItemRow item = (BookmarkItemRow) LayoutInflater.from( parent.getContext()).inflate(R.layout.bookmark_item_row, parent, false); item.onBookmarkDelegateInitialized(mDelegate); return new ItemViewHolder(item); default: assert false; return null; } } @SuppressFBWarnings("BC_UNCONFIRMED_CAST") @Override public void onBindViewHolder(ViewHolder holder, int position) { BookmarkId id = getItem(position); switch (getItemViewType(position)) { case PROMO_HEADER_VIEW: case DIVIDER_VIEW: break; case FOLDER_VIEW: ((BookmarkRow) holder.itemView).setBookmarkId(id); break; case BOOKMARK_VIEW: ((BookmarkRow) holder.itemView).setBookmarkId(id); break; default: assert false : "View type not supported!"; } } // PromoHeaderShowingChangeListener implementation. @Override public void onPromoHeaderShowingChanged(boolean isShowing) { assert mDelegate != null; if (mDelegate.getCurrentState() != BookmarkUIState.STATE_FOLDER) { return; } updateHeader(); updateDividerSections(); notifyDataSetChanged(); } // BookmarkUIObserver implementations. @Override public void onBookmarkDelegateInitialized(BookmarkDelegate delegate) { mDelegate = delegate; mDelegate.addUIObserver(this); mDelegate.getModel().addObserver(mBookmarkModelObserver); mPromoHeaderManager = new BookmarkPromoHeader(mContext, this); } @Override public void onDestroy() { mDelegate.removeUIObserver(this); mDelegate.getModel().removeObserver(mBookmarkModelObserver); mDelegate = null; mPromoHeaderManager.destroy(); } @Override public void onFolderStateSet(BookmarkId folder) { assert mDelegate != null; setBookmarks(mDelegate.getModel().getChildIDs(folder, true, false), mDelegate.getModel().getChildIDs(folder, false, true)); } @Override public void onSelectionStateChange(List<BookmarkId> selectedBookmarks) {} private static class ItemViewHolder extends RecyclerView.ViewHolder { private ItemViewHolder(View view) { super(view); } } private void updateHeader() { if (mDelegate == null) return; int currentUIState = mDelegate.getCurrentState(); if (currentUIState == BookmarkUIState.STATE_LOADING) return; mPromoHeaderSection.clear(); assert currentUIState == BookmarkUIState.STATE_FOLDER : "Unexpected UI state"; if (mPromoHeaderManager.shouldShow()) { mPromoHeaderSection.add(null); } } }