Commit a63e5f7d authored by Theresa's avatar Theresa Committed by Commit Bot

Setup base structure for contextual suggestions UI component

Create the basic MVC components for the contextual suggestions component
and content sub-component. Sets up basic connection to the bottom sheet
and moves some prototype code to the ContextualSuggestionsMediator.

A follow-up CL will remove the old, dead prototype code.

BUG=822842

Change-Id: I01461f5ae4c5ec0cc33dbad31daa1415fe46c2cf
Reviewed-on: https://chromium-review.googlesource.com/967010
Commit-Queue: Theresa <twellington@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544737}
parent 6cd16e29
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 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. -->
<org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:paddingTop="@dimen/bottom_control_container_height" />
\ No newline at end of file
......@@ -69,6 +69,7 @@ import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager.ContextualSearchTabPromotionDelegate;
import org.chromium.chrome.browser.contextualsuggestions.ContextualSuggestionsCoordinator;
import org.chromium.chrome.browser.datausage.DataUseTabUIManager;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.dom_distiller.DomDistillerUIUtils;
......@@ -115,7 +116,6 @@ import org.chromium.chrome.browser.snackbar.DataReductionPromoSnackbarController
import org.chromium.chrome.browser.snackbar.DataUseSnackbarController;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarManageable;
import org.chromium.chrome.browser.suggestions.ContextualSuggestionsManager;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.chrome.browser.sync.SyncController;
import org.chromium.chrome.browser.tab.Tab;
......@@ -258,7 +258,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private ToolbarManager mToolbarManager;
private FindToolbarManager mFindToolbarManager;
private BottomSheet mBottomSheet;
private ContextualSuggestionsManager mContextualSuggestionsManager;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView;
// Time in ms that it took took us to inflate the initial layout
......@@ -1134,6 +1134,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mBottomSheet = null;
}
if (mContextualSuggestionsCoordinator != null) {
mContextualSuggestionsCoordinator.destroy();
mContextualSuggestionsCoordinator = null;
}
if (mTabModelsInitialized) {
TabModelSelector selector = getTabModelSelector();
if (selector != null) selector.destroy();
......@@ -1259,8 +1264,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
});
mFadingBackgroundView.addObserver(mBottomSheet);
mContextualSuggestionsManager = new ContextualSuggestionsManager(
this, mBottomSheet, getTabModelSelector(), getSnackbarManager());
mContextualSuggestionsCoordinator =
new ContextualSuggestionsCoordinator(this, mBottomSheet, getTabModelSelector());
}
}
......
// Copyright 2018 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.contextualsuggestions;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
/**
* Coordinator for the content sub-component. Responsible for communication with the parent
* {@link ContextualSuggestionsCoordinator} and lifecycle of component objects.
*/
class ContentCoordinator {
private final ContextualSuggestionsModel mModel;
private SuggestionsRecyclerView mRecyclerView;
private RecyclerViewModelChangeProcessor<SnippetArticle, ContextualSuggestionCardViewHolder>
mModelChangeProcessor;
/**
* Construct a new {@link ContentCoordinator}.
* @param context The {@link Context} used to retrieve resources.
* @param parentView The parent {@link View} to which the content will eventually be attached.
* @param profile The regular {@link Profile}.
* @param uiDelegate The {@link SuggestionsUiDelegate} used to help construct items in the
* content view.
* @param model The {@link ContextualSuggestionsModel} for the component.
*/
ContentCoordinator(Context context, ViewGroup parentView, Profile profile,
SuggestionsUiDelegate uiDelegate, ContextualSuggestionsModel model) {
mModel = model;
mRecyclerView = (SuggestionsRecyclerView) LayoutInflater.from(context).inflate(
R.layout.contextual_suggestions_layout, parentView, false);
ContextualSuggestionsAdapter adapter = new ContextualSuggestionsAdapter(
context, profile, new UiConfig(mRecyclerView), uiDelegate, mModel);
mRecyclerView.setAdapter(adapter);
mModelChangeProcessor = new RecyclerViewModelChangeProcessor<>(adapter);
mModel.addObserver(mModelChangeProcessor);
}
/** @return The content {@link View}. */
View getView() {
return mRecyclerView;
}
/** @return The vertical scroll offset of the content view. */
int getVerticalScrollOffset() {
return mRecyclerView.computeVerticalScrollOffset();
}
/** Destroy the content component. */
void destroy() {
if (mRecyclerView == null) return;
mRecyclerView.setAdapter(null);
mRecyclerView = null;
mModel.removeObserver(mModelChangeProcessor);
mModelChangeProcessor = null;
}
}
// Copyright 2018 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.contextualsuggestions;
import org.chromium.chrome.browser.ntp.ContextMenuManager;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
/** Holder for a contextual suggestions card. **/
public class ContextualSuggestionCardViewHolder extends SnippetArticleViewHolder {
ContextualSuggestionCardViewHolder(SuggestionsRecyclerView parent,
ContextMenuManager contextMenuManager, SuggestionsUiDelegate uiDelegate,
UiConfig uiConfig, OfflinePageBridge offlinePageBridge) {
super(parent, contextMenuManager, uiDelegate, uiConfig, offlinePageBridge);
}
@Override
public boolean isDismissable() {
return false;
}
}
// Copyright 2018 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.contextualsuggestions;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.ntp.cards.ItemViewType;
import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout;
import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.ContentSuggestionsAdditionalAction;
import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
/**
* An adapter that contains the view binder for the content component.
*/
class ContextualSuggestionsAdapter
extends RecyclerViewAdapter<SnippetArticle, ContextualSuggestionCardViewHolder> {
private class ContextualSuggestionsViewBinder
implements ViewBinder<SnippetArticle, ContextualSuggestionCardViewHolder> {
@Override
public ContextualSuggestionCardViewHolder onCreateViewHolder(
ViewGroup parent, int viewType) {
assert viewType == ItemViewType.SNIPPET;
// TODO(twellington): Hook up ContextMenuManager.
return new ContextualSuggestionCardViewHolder(mRecyclerView, null, mUiDelegate,
mUiConfig, OfflinePageBridge.getForProfile(mProfile));
}
@Override
public void onBindViewHolder(
ContextualSuggestionCardViewHolder holder, SnippetArticle item) {
holder.onBindViewHolder(item, mCategoryInfo);
}
}
private final Profile mProfile;
private final UiConfig mUiConfig;
private final SuggestionsUiDelegate mUiDelegate;
private final SuggestionsCategoryInfo mCategoryInfo;
private SuggestionsRecyclerView mRecyclerView;
/**
* Construct a new {@link ContextualSuggestionsAdapter}.
* @param context The {@link Context} used to retrieve resources.
* @param profile The regular {@link Profile}.
* @param uiConfig The {@link UiConfig} used to adjust view display.
* @param uiDelegate The {@link SuggestionsUiDelegate} used to help construct items in the
* content view.
* @param model The {@link ContextualSuggestionsModel} for the component.
*/
ContextualSuggestionsAdapter(Context context, Profile profile, UiConfig uiConfig,
SuggestionsUiDelegate uiDelegate, ContextualSuggestionsModel model) {
super(model);
setViewBinder(new ContextualSuggestionsViewBinder());
mProfile = profile;
mUiConfig = uiConfig;
mUiDelegate = uiDelegate;
mCategoryInfo = new SuggestionsCategoryInfo(KnownCategories.CONTEXTUAL,
context.getString(R.string.contextual_suggestions_title),
ContentSuggestionsCardLayout.FULL_CARD, ContentSuggestionsAdditionalAction.NONE,
false, "");
}
@Override
@ItemViewType
public int getItemViewType(int position) {
return ItemViewType.SNIPPET;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mRecyclerView = (SuggestionsRecyclerView) recyclerView;
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
mRecyclerView = null;
}
@Override
public void onViewRecycled(ContextualSuggestionCardViewHolder holder) {
holder.recycle();
}
}
// Copyright 2018 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.contextualsuggestions;
import android.view.View;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
/** A {@link BottomSheetContent} that displays contextual suggestions. */
public class ContextualSuggestionsBottomSheetContent implements BottomSheetContent {
private ContentCoordinator mContentCoordinator;
/**
* Construct a new {@link ContextualSuggestionsBottomSheetContent}.
* @param contentCoordinator The {@link ContentCoordinator} that manages content to be
* displayed.
*/
ContextualSuggestionsBottomSheetContent(ContentCoordinator contentCoordinator) {
mContentCoordinator = contentCoordinator;
}
@Override
public View getContentView() {
return mContentCoordinator.getView();
}
@Override
public View getToolbarView() {
return null;
}
@Override
public int getVerticalScrollOffset() {
return mContentCoordinator.getVerticalScrollOffset();
}
@Override
public void destroy() {
mContentCoordinator = null;
}
@Override
public boolean applyDefaultTopPadding() {
return false;
}
}
// Copyright 2018 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.contextualsuggestions;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegateImpl;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegateImpl;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
/**
* The coordinator for the contextual suggestions UI component. Manages communication with other
* parts of the UI-layer and lifecycle of shared component objects.
*
* This parent coordinator manages two sub-components, controlled by {@link ContentCoordinator}
* and {@link ToolbarCoordinator}. These sub-components each have their own views and view binders.
* They share a {@link ContextualSuggestionsMediator} and {@link ContextualSuggestionsModel}.
*/
public class ContextualSuggestionsCoordinator {
private ChromeActivity mActivity;
private BottomSheet mBottomSheet;
private Profile mProfile;
private ContextualSuggestionsModel mModel;
private ContextualSuggestionsMediator mMediator;
private ContentCoordinator mContentCoordinator;
private SuggestionsUiDelegateImpl mUiDelegate;
private ContextualSuggestionsBottomSheetContent mBottomSheetContent;
/**
* Construct a new {@link ContextualSuggestionsCoordinator}.
* @param activity The containing {@link ChromeActivity}.
* @param bottomSheet The {@link BottomSheet} where contextual suggestions will be displayed.
* @param tabModelSelector The {@link TabModelSelector} for the activity.
*/
public ContextualSuggestionsCoordinator(
ChromeActivity activity, BottomSheet bottomSheet, TabModelSelector tabModelSelector) {
mActivity = activity;
mBottomSheet = bottomSheet;
mProfile = Profile.getLastUsedProfile().getOriginalProfile();
mModel = new ContextualSuggestionsModel();
mMediator = new ContextualSuggestionsMediator(mProfile, tabModelSelector, this, mModel);
SuggestionsSource suggestionsSource = mMediator.getSuggestionsSource();
SuggestionsNavigationDelegate navigationDelegate = new SuggestionsNavigationDelegateImpl(
mActivity, mProfile, mBottomSheet, tabModelSelector);
mUiDelegate = new SuggestionsUiDelegateImpl(suggestionsSource, new DummyEventReporter(),
navigationDelegate, mProfile, mBottomSheet,
mActivity.getChromeApplication().getReferencePool(),
mActivity.getSnackbarManager());
}
/** Called when the containing activity is destroyed. */
public void destroy() {
mMediator.destroy();
if (mContentCoordinator != null) mContentCoordinator.destroy();
if (mBottomSheetContent != null) mBottomSheetContent.destroy();
}
/**
* Displays contextual suggestions in the {@link BottomSheet}.
*/
void displaySuggestions() {
// TODO(twellington): Introduce another method that creates bottom sheet content with only
// a toolbar view when suggestions are fist available, and use this method to construct the
// content view when the sheet is opened.
mContentCoordinator =
new ContentCoordinator(mActivity, mBottomSheet, mProfile, mUiDelegate, mModel);
mBottomSheetContent = new ContextualSuggestionsBottomSheetContent(mContentCoordinator);
mBottomSheet.showContent(mBottomSheetContent);
}
/** Removes contextual suggestions from the {@link BottomSheet}. */
void removeSuggestions() {
if (mContentCoordinator != null) {
mContentCoordinator.destroy();
mContentCoordinator = null;
}
if (mBottomSheetContent == null) return;
mBottomSheet.showContent(null);
mBottomSheetContent.destroy();
mBottomSheetContent = null;
}
}
// Copyright 2018 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.contextualsuggestions;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.webkit.URLUtil;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.ui.widget.Toast;
import java.util.ArrayList;
/**
* A mediator for the contextual suggestions UI component responsible for interacting with
* the contextual suggestions backend, updating the model, and communicating with the
* component coordinator(s).
*/
class ContextualSuggestionsMediator {
private ContextualSuggestionsCoordinator mCoordinator;
private ContextualSuggestionsModel mModel;
private SnippetsBridge mBridge;
private final TabModelSelectorTabModelObserver mTabModelObserver;
private final TabObserver mTabObserver;
private Tab mLastTab;
@Nullable
private String mCurrentContextUrl;
/**
* Construct a new {@link ContextualSuggestionsMediator}.
* @param profile The regular {@link Profile}.
* @param tabModelSelector The {@link TabModelSelector} for the containing activity.
* @param coordinator The {@link ContextualSuggestionsCoordinator} for the component.
* @param model The {@link ContextualSuggestionsModel} for the component.
*/
ContextualSuggestionsMediator(Profile profile, TabModelSelector tabModelSelector,
ContextualSuggestionsCoordinator coordinator, ContextualSuggestionsModel model) {
mCoordinator = coordinator;
mModel = model;
mBridge = new SnippetsBridge(Profile.getLastUsedProfile());
// TODO(twellington): Remove the following code for tab observing after triggering logic
// moves to the C++ layer.
mTabObserver = new EmptyTabObserver() {
@Override
public void onUpdateUrl(Tab tab, String url) {
refresh(url);
}
};
mTabModelObserver = new TabModelSelectorTabModelObserver(tabModelSelector) {
@Override
public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {
updateCurrentTab(tab);
}
};
updateCurrentTab(tabModelSelector.getCurrentTab());
}
/**
* @return The {@link SuggestionsSource} used to fetch suggestions.
*
* TODO(twellington): This method is needed to construct {@link SuggestionsUiDelegateImpl} in
* the coordinator. Try to remove this dependency.
*/
SuggestionsSource getSuggestionsSource() {
return mBridge;
}
/** Destroys the mediator. */
void destroy() {
if (mLastTab != null) {
mLastTab.removeObserver(mTabObserver);
mLastTab = null;
}
mTabModelObserver.destroy();
}
// TODO(twellington): Remove this method after triggering logic moves to the C++ layer.
private void refresh(@Nullable final String newUrl) {
if (!URLUtil.isNetworkUrl(newUrl)) {
clearSuggestions();
return;
}
// Do nothing if there are already suggestions in the suggestions list for the new context.
if (isContextTheSame(newUrl)) return;
// Context has changed, so we want to remove any old suggestions from the section.
clearSuggestions();
mCurrentContextUrl = newUrl;
Toast.makeText(ContextUtils.getApplicationContext(), "Fetching suggestions...",
Toast.LENGTH_SHORT)
.show();
mBridge.fetchContextualSuggestions(newUrl, (suggestions) -> {
// Avoiding double fetches causing suggestions for incorrect context.
if (!TextUtils.equals(newUrl, mCurrentContextUrl)) return;
Toast.makeText(ContextUtils.getApplicationContext(),
suggestions.size() + " suggestions fetched", Toast.LENGTH_SHORT)
.show();
if (suggestions.size() > 0) {
mModel.setSuggestions(suggestions);
mCoordinator.displaySuggestions();
};
});
}
private void clearSuggestions() {
mModel.setSuggestions(new ArrayList<SnippetArticle>());
mCoordinator.removeSuggestions();
}
private boolean isContextTheSame(String newUrl) {
return UrlUtilities.urlsMatchIgnoringFragments(newUrl, mCurrentContextUrl);
}
/**
* Update the current tab and refresh suggestions.
* @param tab The current {@link Tab}.
*/
private void updateCurrentTab(Tab tab) {
if (mLastTab != null) mLastTab.removeObserver(mTabObserver);
mLastTab = tab;
if (mLastTab == null) return;
mLastTab.addObserver(mTabObserver);
refresh(mLastTab.getUrl());
}
}
// Copyright 2018 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.contextualsuggestions;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import java.util.ArrayList;
import java.util.List;
/** A model for the contextual suggestions UI component. */
class ContextualSuggestionsModel extends ListObservable<SnippetArticle> {
private List<SnippetArticle> mSuggestions = new ArrayList<>();
/**
* @param suggestions The list of current suggestions. May be an empty list if no
* suggestions are available.
*/
void setSuggestions(List<SnippetArticle> suggestions) {
assert suggestions != null;
if (suggestions.size() == 0 && mSuggestions.size() == 0) return;
int oldLength = getItemCount();
mSuggestions = suggestions;
if (oldLength != 0) notifyItemRangeRemoved(0, oldLength);
if (mSuggestions.size() != 0) {
notifyItemRangeInserted(0, mSuggestions.size());
}
}
/** @return The list of current suggestions. */
List<SnippetArticle> getSuggestions() {
return mSuggestions;
}
@Override
public int getItemCount() {
return mSuggestions.size();
}
@Override
public SnippetArticle getItem(int position) {
assert position < mSuggestions.size();
return mSuggestions.get(position);
}
}
// Copyright 2018 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.contextualsuggestions;
import org.chromium.chrome.browser.ntp.cards.ActionItem;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.suggestions.SuggestionsEventReporter;
import org.chromium.chrome.browser.suggestions.SuggestionsRanker;
/** A dummy {@link SuggestionsEventReporter} to use until a real one replaces it. */
class DummyEventReporter implements SuggestionsEventReporter {
@Override
public void onSurfaceOpened() {}
@Override
public void onPageShown(
int[] categories, int[] suggestionsPerCategory, boolean[] isCategoryVisible) {}
@Override
public void onSuggestionShown(SnippetArticle suggestion) {}
@Override
public void onSuggestionOpened(SnippetArticle suggestion, int windowOpenDisposition,
SuggestionsRanker suggestionsRanker) {}
@Override
public void onSuggestionMenuOpened(SnippetArticle suggestion) {}
@Override
public void onMoreButtonShown(ActionItem category) {}
@Override
public void onMoreButtonClicked(ActionItem category) {}
}
\ No newline at end of file
file://chrome/android/java/src/org/chromium/chrome/browser/ntp/OWNERS
twellington@chromium.org
\ No newline at end of file
// Copyright 2018 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.modelutil;
import android.support.annotation.Nullable;
import org.chromium.base.ObserverList;
/**
* A base class for models maintaining a list of items. Note that ListObservable models do not need
* to be implemented as a list. Internally they may use any structure to organize their items.
*
* @param <E> The type of items held by the model.
*/
public abstract class ListObservable<E> {
/**
* An observer to be notified of changes to a {@link ListObservable}.
*
* @param <E> The type of items held by the observed model.
*/
public interface ListObserver<E> {
/**
* Notifies that {@code count} items starting at position {@code index} under the
* {@code source} have been added.
*
* @param source The list to which items have been added.
* @param index The starting position of the range of added items.
* @param count The number of added items.
*/
void onItemRangeInserted(ListObservable<E> source, int index, int count);
/**
* Notifies that {@code count} items starting at position {@code index} under the
* {@code source} have been removed.
*
* @param source The list from which items have been removed.
* @param index The starting position of the range of removed items.
* @param count The number of removed items.
*/
void onItemRangeRemoved(ListObservable<E> source, int index, int count);
/**
* Notifies that {@code count} items starting at position {@code index} under the
* {@code source} have changed, with an optional payload object.
*
* @param source The list whose items have changed.
* @param index The starting position of the range of changed items.
* @param count The number of changed items.
* @param payload Optional parameter, use {@code null} to identify a "full" update.
*/
void onItemRangeChanged(
ListObservable<E> source, int index, int count, @Nullable Object payload);
}
private final ObserverList<ListObserver<E>> mObservers = new ObserverList<>();
/** @return The total number of items held by the model. */
public abstract int getItemCount();
/**
* Retrieve the item at the specified {@code position}.
*
* @param position The position of the item to retrieve.
* @return The item at the specified {@code position}.
*/
public abstract E getItem(int position);
/**
* @param observer An observer to be notified of changes to the model.
*/
public void addObserver(ListObserver<E> observer) {
mObservers.addObserver(observer);
}
/** @param observer The observer to remove. */
public void removeObserver(ListObserver<E> observer) {
mObservers.removeObserver(observer);
}
/**
* Notifies observers that {@code count} items starting at position {@code index} have been
* added.
*
* @param index The starting position of the range of added items.
* @param count The number of added items.
*/
protected void notifyItemRangeInserted(int index, int count) {
for (ListObserver<E> observer : mObservers) {
observer.onItemRangeInserted(this, index, count);
}
}
/**
* Notifies observes that {@code count} items starting at position {@code index} have been
* removed.
*
* @param index The starting position of the range of removed items.
* @param count The number of removed items.
*/
protected void notifyItemRangeRemoved(int index, int count) {
for (ListObserver<E> observer : mObservers) {
observer.onItemRangeRemoved(this, index, count);
}
}
/**
* Notifies observers that {@code count} items starting at position {@code index} under the
* {@code source} have changed, with an optional payload object.
*
* @param index The starting position of the range of changed items.
* @param count The number of changed items.
* @param payload Optional parameter, use {@code null} to identify a "full" update.
*/
protected void notifyItemRangeChanged(int index, int count, @Nullable Object payload) {
for (ListObserver<E> observer : mObservers) {
observer.onItemRangeChanged(this, index, count, payload);
}
}
}
bauerb@chromium.org
twellington@chromium.org
\ No newline at end of file
// Copyright 2018 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.modelutil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.ViewGroup;
/**
* An adapter that uses a {@link ViewBinder} to bind items in a {@link ListObservable} model to
* {@link ViewHolder}s.
*
* @param <E> The type of items held by the {@link ListObservable} model.
* @param <VH> The {@link ViewHolder} type for the {@link RecyclerView}.
*/
public class RecyclerViewAdapter<E, VH extends ViewHolder> extends RecyclerView.Adapter<VH> {
/**
* A view binder used to bind items in the {@link ListObservable} model to {@link ViewHolder}s.
*
* @param <E> The type of items held by the {@link ListObservable} model.
* @param <VH> The {@link ViewHolder} type for the {@link RecyclerView}.
*/
public interface ViewBinder<E, VH> {
/**
* Called when the {@link RecyclerView} needs a new {@link ViewHolder} of the given
* {@code viewType} to represent an item.
*
* @param parent The {@link ViewGroup} into which the new {@link View} will be added after
* it's bound to an adapter position.
* @param viewType The view type of the new {@link View}.
* @return A new {@link ViewHolder} that holds a {@link View} of the given view type.
*/
VH onCreateViewHolder(ViewGroup parent, int viewType);
/**
* Called to display the {@code item} in the provided {@code holder}.
*
* @param holder The {@link ViewHolder} which should be updated to represent {@code item}.
* @param item The item to be bound.
*/
void onBindViewHolder(VH holder, E item);
}
protected final ListObservable<E> mModel;
private ViewBinder<E, VH> mViewBinder;
/**
* Construct a new {@link RecyclerViewAdapter}.
* @param model The {@link ListObservable} model used to retrieve items to display in the
* {@link RecyclerView}.
*/
public RecyclerViewAdapter(ListObservable<E> model) {
mModel = model;
}
/**
* Set the {@link ViewBinder} to use with this adapter.
*
* @param viewBinder The {@link ViewBinder} used to bind items in the {@link ListObservable}
* model to {@link ViewHolder}s.
*/
public void setViewBinder(ViewBinder<E, VH> viewBinder) {
mViewBinder = viewBinder;
}
@Override
public int getItemCount() {
return mModel.getItemCount();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return mViewBinder.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(VH holder, int position) {
mViewBinder.onBindViewHolder(holder, mModel.getItem(position));
}
}
// Copyright 2018 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.modelutil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import org.chromium.chrome.browser.modelutil.ListObservable.ListObserver;
/**
* A model change processor for use with a {@link RecyclerView}. The
* {@link RecyclerViewModelChangeProcessor} should be registered as an observer of a
* {@link ListObservable} model. Notifies the associated {@link RecyclerViewAdapter<E, VH>} of
* changes to the model.
*
* @param <E> The type of items held by the {@link ListObservable} model.
* @param <VH> The {@link ViewHolder} type for the RecyclerView.
*/
public class RecyclerViewModelChangeProcessor<E, VH extends ViewHolder> implements ListObserver<E> {
private RecyclerViewAdapter<E, VH> mAdapter;
/**
* Constructs a new {@link RecyclerViewModelChangeProcessor}.
* @param adapter The {@link RecyclerViewAdapter<E, VH>} to be notified of changes to a
* {@link ListObservable} model.
*/
public RecyclerViewModelChangeProcessor(RecyclerViewAdapter<E, VH> adapter) {
mAdapter = adapter;
}
@Override
public void onItemRangeInserted(ListObservable<E> source, int index, int count) {
mAdapter.notifyItemRangeInserted(index, count);
}
@Override
public void onItemRangeRemoved(ListObservable<E> source, int index, int count) {
mAdapter.notifyItemRangeRemoved(index, count);
}
@Override
public void onItemRangeChanged(ListObservable<E> source, int index, int count, Object payload) {
mAdapter.notifyItemRangeChanged(index, count, payload);
}
}
......@@ -289,6 +289,14 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContentCoordinator.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionsAdapter.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionsBottomSheetContent.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionCardViewHolder.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionsCoordinator.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionsMediator.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/ContextualSuggestionsModel.java",
"java/src/org/chromium/chrome/browser/contextualsuggestions/DummyEventReporter.java",
"java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
"java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
"java/src/org/chromium/chrome/browser/coordinator/CoordinatorLayoutForPointer.java",
......@@ -651,6 +659,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
"java/src/org/chromium/chrome/browser/modelutil/ListObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java",
"java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment