Commit 7f1a5c00 authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

[Feed] Move scroll logic to Mediator

Bug: 863171
Change-Id: Ibf9c93098fef18bbda60370d6bdd771442debd9f
Reviewed-on: https://chromium-review.googlesource.com/1145788Reviewed-by: default avatarTheresa <twellington@chromium.org>
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577209}
parent a99a420b
...@@ -15,7 +15,6 @@ import android.widget.FrameLayout; ...@@ -15,7 +15,6 @@ import android.widget.FrameLayout;
import com.google.android.libraries.feed.api.scope.FeedProcessScope; import com.google.android.libraries.feed.api.scope.FeedProcessScope;
import com.google.android.libraries.feed.api.scope.FeedStreamScope; import com.google.android.libraries.feed.api.scope.FeedStreamScope;
import com.google.android.libraries.feed.api.stream.ScrollListener;
import com.google.android.libraries.feed.api.stream.Stream; import com.google.android.libraries.feed.api.stream.Stream;
import com.google.android.libraries.feed.host.stream.CardConfiguration; import com.google.android.libraries.feed.host.stream.CardConfiguration;
import com.google.android.libraries.feed.host.stream.SnackbarApi; import com.google.android.libraries.feed.host.stream.SnackbarApi;
...@@ -45,13 +44,10 @@ import java.util.Arrays; ...@@ -45,13 +44,10 @@ import java.util.Arrays;
/** /**
* Provides a new tab page that displays an interest feed rendered list of content suggestions. * Provides a new tab page that displays an interest feed rendered list of content suggestions.
*/ */
public class FeedNewTabPage public class FeedNewTabPage extends NewTabPage implements TouchEnabledDelegate {
extends NewTabPage implements TouchEnabledDelegate, NewTabPageLayout.ScrollDelegate {
private final FeedNewTabPageMediator mMediator; private final FeedNewTabPageMediator mMediator;
private final StreamLifecycleManager mStreamLifecycleManager; private final StreamLifecycleManager mStreamLifecycleManager;
private final Stream mStream; private final Stream mStream;
private final ScrollListener mStreamScrollListener;
private final SnapScrollHelper mSnapScrollHelper;
private FrameLayout mRootView; private FrameLayout mRootView;
private SectionHeaderView mSectionHeaderView; private SectionHeaderView mSectionHeaderView;
...@@ -152,44 +148,14 @@ public class FeedNewTabPage ...@@ -152,44 +148,14 @@ public class FeedNewTabPage
mStream = streamScope.getStream(); mStream = streamScope.getStream();
mStreamLifecycleManager = new StreamLifecycleManager(mStream, activity, tab); mStreamLifecycleManager = new StreamLifecycleManager(mStream, activity, tab);
mSnapScrollHelper =
new SnapScrollHelper(mNewTabPageManager, mNewTabPageLayout, mStream.getView());
mSectionHeaderView = (SectionHeaderView) LayoutInflater.from(activity).inflate(
R.layout.new_tab_page_snippets_expandable_header, null);
mMediator = new FeedNewTabPageMediator(this);
mStream.getView().setBackgroundColor(Color.WHITE);
mRootView.addView(mStream.getView());
mStream.setHeaderViews(Arrays.asList(mNewTabPageLayout, mSectionHeaderView));
// Listen for layout changes on the NewTabPageView itself to catch changes in scroll LayoutInflater inflater = LayoutInflater.from(activity);
// position that are due to layout changes after e.g. device rotation. This contrasts with mNewTabPageLayout = (NewTabPageLayout) inflater.inflate(R.layout.new_tab_page_layout, null);
// regular scrolling, which is observed through an OnScrollListener. mSectionHeaderView = (SectionHeaderView) inflater.inflate(
mRootView.addOnLayoutChangeListener( R.layout.new_tab_page_snippets_expandable_header, null);
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mSnapScrollHelper.handleScroll();
});
mStreamScrollListener = new ScrollListener() {
@Override
public void onScrollStateChanged(int state) {}
@Override
public void onScrolled(int dx, int dy) {
mSnapScrollHelper.handleScroll();
}
};
mStream.addScrollListener(mStreamScrollListener);
}
@Override mMediator = new FeedNewTabPageMediator(this,
protected void initializeMainView(Context context) { new SnapScrollHelper(mNewTabPageManager, mNewTabPageLayout, mStream.getView()));
int topPadding = context.getResources().getDimensionPixelOffset(R.dimen.tab_strip_height);
mRootView = new FrameLayout(context);
mRootView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
mRootView.setPadding(0, topPadding, 0, 0);
// Don't store a direct reference to the activity, because it might change later if the tab // Don't store a direct reference to the activity, because it might change later if the tab
// is reparented. // is reparented.
...@@ -200,19 +166,30 @@ public class FeedNewTabPage ...@@ -200,19 +166,30 @@ public class FeedNewTabPage
this::setTouchEnabled, closeContextMenuCallback); this::setTouchEnabled, closeContextMenuCallback);
mTab.getWindowAndroid().addContextMenuCloseListener(contextMenuManager); mTab.getWindowAndroid().addContextMenuCloseListener(contextMenuManager);
LayoutInflater inflater = LayoutInflater.from(context);
mNewTabPageLayout = (NewTabPageLayout) inflater.inflate(R.layout.new_tab_page_layout, null);
mNewTabPageLayout.initialize(mNewTabPageManager, mTab, mTileGroupDelegate, mNewTabPageLayout.initialize(mNewTabPageManager, mTab, mTileGroupDelegate,
mSearchProviderHasLogo, mSearchProviderHasLogo,
TemplateUrlService.getInstance().isDefaultSearchEngineGoogle(), this, TemplateUrlService.getInstance().isDefaultSearchEngineGoogle(), mMediator,
contextMenuManager, new UiConfig(mRootView)); contextMenuManager, new UiConfig(mRootView));
mStream.getView().setBackgroundColor(Color.WHITE);
mRootView.addView(mStream.getView());
mStream.setHeaderViews(Arrays.asList(mNewTabPageLayout, mSectionHeaderView));
}
@Override
protected void initializeMainView(Context context) {
int topPadding = context.getResources().getDimensionPixelOffset(R.dimen.tab_strip_height);
mRootView = new FrameLayout(context);
mRootView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
mRootView.setPadding(0, topPadding, 0, 0);
} }
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();
mMediator.destroy(); mMediator.destroy();
mStream.removeScrollListener(mStreamScrollListener);
mImageLoader.destroy(); mImageLoader.destroy();
mStreamLifecycleManager.destroy(); mStreamLifecycleManager.destroy();
} }
...@@ -261,40 +238,4 @@ public class FeedNewTabPage ...@@ -261,40 +238,4 @@ public class FeedNewTabPage
public void setTouchEnabled(boolean enabled) { public void setTouchEnabled(boolean enabled) {
// TODO(twellington): implement this method. // TODO(twellington): implement this method.
} }
// ScrollDelegate interface.
@Override
public boolean isScrollViewInitialized() {
// During startup the view may not be fully initialized, so we check to see if some basic
// view properties (height of the RecyclerView) are sane.
return mStream != null && mStream.getView().getHeight() > 0;
}
@Override
public int getVerticalScrollOffset() {
// This method returns a valid vertical scroll offset only when the first header view in the
// Stream is visible. In all other cases, this returns 0.
if (!isScrollViewInitialized()) return 0;
int firstChildTop = mStream.getChildTopAt(0);
return firstChildTop != Stream.POSITION_NOT_KNOWN ? -firstChildTop : 0;
}
@Override
public boolean isChildVisibleAtPosition(int position) {
return isScrollViewInitialized() && mStream.isChildAtPositionVisible(position);
}
@Override
public void snapScroll() {
if (!isScrollViewInitialized()) return;
int initialScroll = getVerticalScrollOffset();
int scrollTo = mSnapScrollHelper.calculateSnapPosition(initialScroll);
// Calculating the snap position should be idempotent.
assert scrollTo == mSnapScrollHelper.calculateSnapPosition(scrollTo);
mStream.smoothScrollBy(0, scrollTo - initialScroll);
}
} }
...@@ -6,7 +6,12 @@ package org.chromium.chrome.browser.feed; ...@@ -6,7 +6,12 @@ package org.chromium.chrome.browser.feed;
import android.content.res.Resources; import android.content.res.Resources;
import com.google.android.libraries.feed.api.stream.ScrollListener;
import com.google.android.libraries.feed.api.stream.Stream;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.NewTabPageLayout;
import org.chromium.chrome.browser.ntp.SnapScrollHelper;
import org.chromium.chrome.browser.ntp.snippets.SectionHeader; import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar; import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
...@@ -16,17 +21,20 @@ import org.chromium.chrome.browser.preferences.PrefServiceBridge; ...@@ -16,17 +21,20 @@ import org.chromium.chrome.browser.preferences.PrefServiceBridge;
* A mediator for the {@link FeedNewTabPage} responsible for interacting with the * A mediator for the {@link FeedNewTabPage} responsible for interacting with the
* native library and handling business logic. * native library and handling business logic.
*/ */
class FeedNewTabPageMediator { class FeedNewTabPageMediator implements NewTabPageLayout.ScrollDelegate {
private final FeedNewTabPage mCoordinator; private final FeedNewTabPage mCoordinator;
private final SnapScrollHelper mSnapScrollHelper;
private final PrefChangeRegistrar mPrefChangeRegistrar; private final PrefChangeRegistrar mPrefChangeRegistrar;
private ScrollListener mStreamScrollListener;
private SectionHeader mSectionHeader; private SectionHeader mSectionHeader;
/** /**
* @param feedNewTabPage The {@link FeedNewTabPage} that interacts with this class. * @param feedNewTabPage The {@link FeedNewTabPage} that interacts with this class.
*/ */
FeedNewTabPageMediator(FeedNewTabPage feedNewTabPage) { FeedNewTabPageMediator(FeedNewTabPage feedNewTabPage, SnapScrollHelper snapScrollHelper) {
mCoordinator = feedNewTabPage; mCoordinator = feedNewTabPage;
mSnapScrollHelper = snapScrollHelper;
initializeProperties(); initializeProperties();
mPrefChangeRegistrar = new PrefChangeRegistrar(); mPrefChangeRegistrar = new PrefChangeRegistrar();
...@@ -36,6 +44,7 @@ class FeedNewTabPageMediator { ...@@ -36,6 +44,7 @@ class FeedNewTabPageMediator {
/** Clears any dependencies. */ /** Clears any dependencies. */
void destroy() { void destroy() {
mCoordinator.getStream().removeScrollListener(mStreamScrollListener);
mPrefChangeRegistrar.destroy(); mPrefChangeRegistrar.destroy();
} }
...@@ -44,6 +53,25 @@ class FeedNewTabPageMediator { ...@@ -44,6 +53,25 @@ class FeedNewTabPageMediator {
* TODO(huayinz): Introduce a Model for these properties. * TODO(huayinz): Introduce a Model for these properties.
*/ */
private void initializeProperties() { private void initializeProperties() {
// Listen for layout changes on the NewTabPageView itself to catch changes in scroll
// position that are due to layout changes after e.g. device rotation. This contrasts with
// regular scrolling, which is observed through an OnScrollListener.
mCoordinator.getView().addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mSnapScrollHelper.handleScroll();
});
mStreamScrollListener = new ScrollListener() {
@Override
public void onScrollStateChanged(int state) {}
@Override
public void onScrolled(int dx, int dy) {
mSnapScrollHelper.handleScroll();
}
};
mCoordinator.getStream().addScrollListener(mStreamScrollListener);
Resources res = mCoordinator.getSectionHeaderView().getResources(); Resources res = mCoordinator.getSectionHeaderView().getResources();
mSectionHeader = mSectionHeader =
new SectionHeader(res.getString(R.string.ntp_article_suggestions_section_header), new SectionHeader(res.getString(R.string.ntp_article_suggestions_section_header),
...@@ -72,4 +100,42 @@ class FeedNewTabPageMediator { ...@@ -72,4 +100,42 @@ class FeedNewTabPageMediator {
// TODO(huayinz): Update the section header view through a ModelChangeProcessor. // TODO(huayinz): Update the section header view through a ModelChangeProcessor.
mCoordinator.getSectionHeaderView().updateIconDrawable(); mCoordinator.getSectionHeaderView().updateIconDrawable();
} }
// ScrollDelegate interface.
@Override
public boolean isScrollViewInitialized() {
Stream stream = mCoordinator.getStream();
// During startup the view may not be fully initialized, so we check to see if some basic
// view properties (height of the RecyclerView) are sane.
return stream != null && stream.getView().getHeight() > 0;
}
@Override
public int getVerticalScrollOffset() {
// This method returns a valid vertical scroll offset only when the first header view in the
// Stream is visible. In all other cases, this returns 0.
if (!isScrollViewInitialized()) return 0;
int firstChildTop = mCoordinator.getStream().getChildTopAt(0);
return firstChildTop != Stream.POSITION_NOT_KNOWN ? -firstChildTop : 0;
}
@Override
public boolean isChildVisibleAtPosition(int position) {
return isScrollViewInitialized()
&& mCoordinator.getStream().isChildAtPositionVisible(position);
}
@Override
public void snapScroll() {
if (!isScrollViewInitialized()) return;
int initialScroll = getVerticalScrollOffset();
int scrollTo = mSnapScrollHelper.calculateSnapPosition(initialScroll);
// Calculating the snap position should be idempotent.
assert scrollTo == mSnapScrollHelper.calculateSnapPosition(scrollTo);
mCoordinator.getStream().smoothScrollBy(0, scrollTo - initialScroll);
}
} }
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