diff --git a/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java b/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java index e87590f12..cf505dcd6 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java @@ -7,6 +7,9 @@ import android.view.ScaleGestureDetector; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /* The GestureHelper present a simple gesture api for the PdfViewer */ @@ -14,6 +17,7 @@ class GestureHelper { public interface GestureListener { boolean onTapUp(); + boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY); void onZoom(float scaleFactor, float focusX, float focusY); void onZoomEnd(); } @@ -21,14 +25,6 @@ public interface GestureListener { @SuppressLint("ClickableViewAccessibility") static void attach(Context context, View gestureView, GestureListener listener) { - final GestureDetector detector = new GestureDetector(context, - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - return listener.onTapUp(); - } - }); - final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override @@ -45,6 +41,29 @@ public void onScaleEnd(ScaleGestureDetector detector) { } }); + final GestureDetector detector = new GestureDetector(context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(@NonNull MotionEvent motionEvent) { + return listener.onTapUp(); + } + + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + if (scaleDetector.isInProgress()) { + return false; + } + + // Ignore multi-touch + if (e1 != null && (e1.getPointerCount() > 1 || e2.getPointerCount() > 1)) { + return false; + } + + return listener.onFling(e1, e2, velocityX, velocityY); + } + }); + gestureView.setOnTouchListener((view, motionEvent) -> { detector.onTouchEvent(motionEvent); scaleDetector.onTouchEvent(motionEvent); diff --git a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java index 936f70851..0ac63fb19 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java @@ -13,7 +13,9 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.RenderProcessGoneDetail; @@ -28,6 +30,7 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.WindowCompat; import androidx.fragment.app.Fragment; @@ -125,6 +128,8 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader private float mZoomRatio = 1f; private float mZoomFocusX = 0f; private float mZoomFocusY = 0f; + private int swipeThreshold; + private int swipeVelocityThreshold; private int mDocumentOrientationDegrees; private int mDocumentState; private String mEncryptedDocumentPassword; @@ -431,6 +436,8 @@ public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) } }); + initializeGestures(); + GestureHelper.attach(PdfViewer.this, binding.webview, new GestureHelper.GestureListener() { @Override @@ -450,6 +457,39 @@ public boolean onTapUp() { return false; } + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { + if (e1 == null) return false; + + float deltaX = e2.getX() - e1.getX(); + float deltaY = e2.getY() - e1.getY(); + float absDeltaX = Math.abs(deltaX); + float absDeltaY = Math.abs(deltaY); + + // Check primarily horizontal + if (absDeltaX > absDeltaY && + absDeltaX > swipeThreshold && + Math.abs(velocityX) > swipeVelocityThreshold) { + + boolean swipeLeft = deltaX < 0; + boolean swipeRight = deltaX > 0; + + // Edge detection + boolean atLeftEdge = !binding.webview.canScrollHorizontally(-1); + boolean atRightEdge = !binding.webview.canScrollHorizontally(1); + + if (swipeLeft && atRightEdge) { + onJumpToPageInDocument(mPage + 1); + return true; + } else if (swipeRight && atLeftEdge) { + onJumpToPageInDocument(mPage - 1); + return true; + } + } + + return false; + } + @Override public void onZoom(float scaleFactor, float focusX, float focusY) { zoom(scaleFactor, focusX, focusY, false); @@ -520,6 +560,12 @@ public void onZoomEnd() { } } + private void initializeGestures() { + ViewConfiguration vc = ViewConfiguration.get(this); + swipeThreshold = vc.getScaledTouchSlop() * 4; + swipeVelocityThreshold = vc.getScaledMinimumFlingVelocity(); + } + private void purgeWebView() { binding.webview.removeJavascriptInterface("channel"); binding.getRoot().removeView(binding.webview);