Commit 418743b1 authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

[Paint Preview] Reload on pull down

This adds swipe to refresh behavior for the paint preview player. At
present this triggers a tab reload. However, this behavior can be
updated/customized in future for different uses.

Bug: 1086466
Change-Id: I4454fae7f6f996cccc39c75566335485254b375b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2215284
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: default avatarMehran Mahmoudi <mahmoudi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772768}
parent f4debacc
......@@ -53,7 +53,8 @@ public class PaintPreviewDemoManager implements TabViewProvider {
if (success) {
mPlayerManager = new PlayerManager(mTab.getUrl(), mTab.getContext(),
mPaintPreviewDemoService, String.valueOf(mTab.getId()),
PaintPreviewDemoManager.this::onLinkClicked, safeToShow -> {
PaintPreviewDemoManager.this::onLinkClicked,
PaintPreviewDemoManager.this::removePaintPreviewDemo, safeToShow -> {
addPlayerView(safeToShow);
}, TabThemeColorHelper.getBackgroundColor(mTab));
}
......
......@@ -41,7 +41,8 @@ public class TabbedPaintPreviewPlayer {
if (hasCapture) {
mPlayerManager = new PlayerManager(mTab.getUrl(), mTab.getContext(),
mPaintPreviewTabService, String.valueOf(mTab.getId()),
TabbedPaintPreviewPlayer.this::onLinkClicked, safeToShow -> {
TabbedPaintPreviewPlayer.this::onLinkClicked,
TabbedPaintPreviewPlayer.this::onRefresh, safeToShow -> {
addPlayerView(safeToShow, shownCallback);
}, TabThemeColorHelper.getBackgroundColor(mTab));
}
......@@ -79,4 +80,11 @@ public class TabbedPaintPreviewPlayer {
mTab.loadUrl(new LoadUrlParams(url.getSpec()));
removePaintPreview();
}
private void onRefresh() {
if (mTab == null) return;
mTab.reload();
removePaintPreview();
}
}
......@@ -48,10 +48,12 @@ android_library("java") {
sources = [
"java/src/org/chromium/components/paintpreview/player/LinkClickHandler.java",
"java/src/org/chromium/components/paintpreview/player/OverscrollHandler.java",
"java/src/org/chromium/components/paintpreview/player/PaintPreviewFrame.java",
"java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegate.java",
"java/src/org/chromium/components/paintpreview/player/PlayerCompositorDelegateImpl.java",
"java/src/org/chromium/components/paintpreview/player/PlayerManager.java",
"java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java",
"java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java",
"java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameCoordinator.java",
"java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameGestureDetector.java",
......@@ -66,7 +68,9 @@ android_library("java") {
"//base:base_java",
"//base:jni_java",
"//components/paint_preview/browser/android:java",
"//third_party/android_swipe_refresh:android_swipe_refresh_java",
"//ui/android:ui_java",
"//ui/android:ui_java_resources",
"//url:gurl_java",
]
}
......
// Copyright 2020 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.components.paintpreview.player;
/**
* Interface for handling overscroll events in the player.
*/
public interface OverscrollHandler {
/**
* Used to start an overscroll event. Returns true if it is able to be created/consumed.
*/
boolean start();
/**
* Updates the overscroll amount.
*
* @param yDelta The change in overscroll amount. Positive values indicate more overscrolling.
*/
void pull(float yDelta);
/**
* Releases the overscroll event. This will trigger a refresh if a sufficient number and
* distance of {@link #pull} calls occurred.
*/
void release();
/**
* Resets the overscroll event if it was aborted.
*/
void reset();
}
......@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This is the only public class in this package and is hence the access point of this component for
......@@ -36,16 +37,21 @@ public class PlayerManager {
private FrameLayout mHostView;
private Callback<Boolean> mViewReadyCallback;
private static final String sInitEvent = "paint_preview PlayerManager init";
private PlayerSwipeRefreshHandler mPlayerSwipeRefreshHandler;
public PlayerManager(GURL url, Context context,
NativePaintPreviewServiceProvider nativePaintPreviewServiceProvider,
String directoryKey, @Nonnull LinkClickHandler linkClickHandler,
Callback<Boolean> viewReadyCallback, int backgroundColor) {
@Nullable Runnable refreshCallback, Callback<Boolean> viewReadyCallback,
int backgroundColor) {
TraceEvent.startAsync(sInitEvent, hashCode());
mContext = context;
mDelegate = new PlayerCompositorDelegateImpl(nativePaintPreviewServiceProvider, url,
directoryKey, this::onCompositorReady, linkClickHandler);
mHostView = new FrameLayout(mContext);
if (refreshCallback != null) {
mPlayerSwipeRefreshHandler = new PlayerSwipeRefreshHandler(mContext, refreshCallback);
}
mHostView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mHostView.setBackgroundColor(backgroundColor);
......@@ -70,11 +76,15 @@ public class PlayerManager {
frameContentSize, subFramesCount, subFrameGuids, subFrameClipRects);
mRootFrameCoordinator = new PlayerFrameCoordinator(mContext, mDelegate, rootFrame.getGuid(),
rootFrame.getContentWidth(), rootFrame.getContentHeight(), true);
rootFrame.getContentWidth(), rootFrame.getContentHeight(), true,
mPlayerSwipeRefreshHandler);
buildSubFrameCoordinators(mRootFrameCoordinator, rootFrame);
mHostView.addView(mRootFrameCoordinator.getView(),
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
if (mPlayerSwipeRefreshHandler != null) {
mHostView.addView(mPlayerSwipeRefreshHandler.getView());
}
TraceEvent.finishAsync(sInitEvent, hashCode());
mViewReadyCallback.onResult(safeToShow);
}
......@@ -130,9 +140,9 @@ public class PlayerManager {
for (int i = 0; i < frame.getSubFrames().length; i++) {
PaintPreviewFrame childFrame = frame.getSubFrames()[i];
PlayerFrameCoordinator childCoordinator =
new PlayerFrameCoordinator(mContext, mDelegate, childFrame.getGuid(),
childFrame.getContentWidth(), childFrame.getContentHeight(), false);
PlayerFrameCoordinator childCoordinator = new PlayerFrameCoordinator(mContext,
mDelegate, childFrame.getGuid(), childFrame.getContentWidth(),
childFrame.getContentHeight(), false, null);
buildSubFrameCoordinators(childCoordinator, childFrame);
frameCoordinator.addSubFrame(childCoordinator, frame.getSubFrameClips()[i]);
}
......
// Copyright 2020 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.components.paintpreview.player;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import org.chromium.third_party.android.swiperefresh.SwipeRefreshLayout;
import javax.annotation.Nonnull;
/**
* A class for handling overscroll to refresh behavior for the Paint Preview player. This is based
* on the modified version of the Android compat library's SwipeRefreshLayout due to the Player's
* FrameLayout not behaving like a normal scrolling view.
*/
public class PlayerSwipeRefreshHandler implements OverscrollHandler {
// The duration of the refresh animation after a refresh signal.
private static final int STOP_REFRESH_ANIMATION_DELAY_MS = 500;
// The modified AppCompat version of the refresh effect.
private SwipeRefreshLayout mSwipeRefreshLayout;
// A handler to delegate refreshes event to.
private Runnable mRefreshCallback;
/*
* Constructs a new instance of the handler.
*
* @param context The Context to create tha handler for.
* @param refreshCallback The handler that refresh events are delegated to.
*/
public PlayerSwipeRefreshHandler(Context context, @Nonnull Runnable refreshCallback) {
mRefreshCallback = refreshCallback;
mSwipeRefreshLayout = new SwipeRefreshLayout(context);
mSwipeRefreshLayout.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
// Use the same colors as {@link org.chromium.chrome.browser.SwipeRefreshHandler}.
mSwipeRefreshLayout.setProgressBackgroundColorSchemeResource(
org.chromium.ui.R.color.default_bg_color_elev_2);
mSwipeRefreshLayout.setColorSchemeResources(
org.chromium.ui.R.color.default_control_color_active);
mSwipeRefreshLayout.setEnabled(true);
mSwipeRefreshLayout.setOnRefreshListener(() -> {
mSwipeRefreshLayout.postDelayed(() -> {
mSwipeRefreshLayout.setRefreshing(false);
}, STOP_REFRESH_ANIMATION_DELAY_MS);
mRefreshCallback.run();
});
}
/*
* Gets the view that contains the swipe to refresh animations.
*/
public View getView() {
return mSwipeRefreshLayout;
}
@Override
public boolean start() {
return mSwipeRefreshLayout.start();
}
@Override
public void pull(float yDelta) {
mSwipeRefreshLayout.pull(yDelta);
}
@Override
public void release() {
mSwipeRefreshLayout.release(true);
}
@Override
public void reset() {
mSwipeRefreshLayout.reset();
}
}
......@@ -10,10 +10,13 @@ import android.view.View;
import android.widget.Scroller;
import org.chromium.base.UnguessableToken;
import org.chromium.components.paintpreview.player.OverscrollHandler;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import javax.annotation.Nullable;
/**
* Sets up the view and the logic behind it for a Paint Preview frame.
*/
......@@ -26,12 +29,15 @@ public class PlayerFrameCoordinator {
* binds them together.
*/
public PlayerFrameCoordinator(Context context, PlayerCompositorDelegate compositorDelegate,
UnguessableToken frameGuid, int contentWidth, int contentHeight,
boolean canDetectZoom) {
UnguessableToken frameGuid, int contentWidth, int contentHeight, boolean canDetectZoom,
@Nullable OverscrollHandler overscrollHandler) {
PropertyModel model = new PropertyModel.Builder(PlayerFrameProperties.ALL_KEYS).build();
mMediator = new PlayerFrameMediator(model, compositorDelegate, new Scroller(context),
frameGuid, contentWidth, contentHeight);
mView = new PlayerFrameView(context, canDetectZoom, mMediator);
if (overscrollHandler != null) {
mMediator.setOverscrollHandler(overscrollHandler);
}
PropertyModelChangeProcessor.create(model, mView, PlayerFrameViewBinder::bind);
}
......
......@@ -64,6 +64,9 @@ class PlayerFrameGestureDetector
mScaleGestureDetector.onTouchEvent(event);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
mPlayerFrameViewDelegate.onRelease();
}
return mGestureDetector.onTouchEvent(event);
}
......
......@@ -14,6 +14,7 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.UnguessableToken;
import org.chromium.components.paintpreview.player.OverscrollHandler;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -89,6 +90,11 @@ class PlayerFrameMediator implements PlayerFrameViewDelegate {
/** The current scale factor. */
private float mScaleFactor;
/** For swipe-to-refresh logic */
private OverscrollHandler mOverscrollHandler;
private boolean mIsOverscrolling = false;
private float mOverscrollAmount = 0.0f;
PlayerFrameMediator(PropertyModel model, PlayerCompositorDelegate compositorDelegate,
Scroller scroller, UnguessableToken frameGuid, int contentWidth, int contentHeight) {
mModel = model;
......@@ -299,10 +305,46 @@ class PlayerFrameMediator implements PlayerFrameViewDelegate {
@Override
public boolean scrollBy(float distanceX, float distanceY) {
mScroller.forceFinished(true);
return scrollByInternal(distanceX, distanceY);
}
@Override
public void onRelease() {
if (mOverscrollHandler == null || !mIsOverscrolling) return;
mOverscrollHandler.release();
mIsOverscrolling = false;
mOverscrollAmount = 0.0f;
}
private boolean maybeHandleOverscroll(float distanceY) {
if (mOverscrollHandler == null || mViewportRect.top != 0) return false;
// Ignore if there is no active overscroll and the direction is down.
if (!mIsOverscrolling && distanceY <= 0) return false;
mOverscrollAmount += distanceY;
// If the overscroll is completely eased off the cancel the event.
if (mOverscrollAmount <= 0) {
mIsOverscrolling = false;
mOverscrollHandler.reset();
return false;
}
// Start the overscroll event if the scroll direction is correct and one isn't active.
if (!mIsOverscrolling && distanceY > 0) {
mOverscrollAmount = distanceY;
mIsOverscrolling = mOverscrollHandler.start();
}
mOverscrollHandler.pull(distanceY);
return mIsOverscrolling;
}
private boolean scrollByInternal(float distanceX, float distanceY) {
if (maybeHandleOverscroll(-distanceY)) return true;
int validDistanceX = 0;
int validDistanceY = 0;
float scaledContentWidth = mContentWidth * mScaleFactor;
......@@ -353,6 +395,10 @@ class PlayerFrameMediator implements PlayerFrameViewDelegate {
return true;
}
public void setOverscrollHandler(OverscrollHandler overscrollHandler) {
mOverscrollHandler = overscrollHandler;
}
/**
* Handles a fling update by computing the next scroll offset and programmatically scrolling.
*/
......
......@@ -39,4 +39,9 @@ interface PlayerFrameViewDelegate {
* @return Whether this fling was consumed.
*/
boolean onFling(float velocityX, float velocityY);
/**
* Called when a gesture is released.
*/
void onRelease();
}
......@@ -51,6 +51,7 @@ public class PaintPreviewPlayerTest extends DummyUiActivityTestCase {
private PlayerManager mPlayerManager;
private TestLinkClickHandler mLinkClickHandler;
private CallbackHelper mRefreshedCallback;
/**
* LinkClickHandler implementation for caching the last URL that was clicked.
......@@ -126,23 +127,48 @@ public class PaintPreviewPlayerTest extends DummyUiActivityTestCase {
assertLinkUrl(playerHostView, 422, 4965, TEST_OUT_OF_VIEWPORT_LINK_URL);
}
/**
* Scrolls to the bottom fo the paint preview.
*/
private void scrollToBottom() {
@Test
@MediumTest
public void overscrollRefreshTest() throws Exception {
initPlayerManager();
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
int deviceHeight = uiDevice.getDisplayHeight();
int statusBarHeight = statusBarHeight();
int navigationBarHeight = navigationBarHeight();
int padding = 20;
int toY = deviceHeight - navigationBarHeight - padding;
int fromY = statusBarHeight + padding;
uiDevice.swipe(50, fromY, 50, toY, 5);
mRefreshedCallback.waitForFirst();
}
private int statusBarHeight() {
Rect visibleContentRect = new Rect();
getActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleContentRect);
int statusBarHeight = visibleContentRect.top;
return visibleContentRect.top;
}
private int navigationBarHeight() {
int navigationBarHeight = 100;
int resourceId = getActivity().getResources().getIdentifier(
"navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = getActivity().getResources().getDimensionPixelSize(resourceId);
}
return navigationBarHeight;
}
/**
* Scrolls to the bottom fo the paint preview.
*/
private void scrollToBottom() {
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
int deviceHeight = uiDevice.getDisplayHeight();
int statusBarHeight = statusBarHeight();
int navigationBarHeight = navigationBarHeight();
int padding = 20;
int swipeSteps = 5;
......@@ -160,11 +186,13 @@ public class PaintPreviewPlayerTest extends DummyUiActivityTestCase {
private void initPlayerManager() {
mLinkClickHandler = new TestLinkClickHandler();
mRefreshedCallback = new CallbackHelper();
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
PaintPreviewTestService service =
new PaintPreviewTestService(UrlUtils.getIsolatedTestFilePath(TEST_DATA_DIR));
mPlayerManager = new PlayerManager(new GURL(TEST_URL), getActivity(), service,
TEST_DIRECTORY_KEY, mLinkClickHandler, Assert::assertTrue, 0xffffffff);
TEST_DIRECTORY_KEY, mLinkClickHandler,
() -> { mRefreshedCallback.notifyCalled(); }, Assert::assertTrue, 0xffffffff);
getActivity().setContentView(mPlayerManager.getView());
});
......
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