diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f313df95..c3aa352aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ All notable changes to this project will be documented in this file. Take a look * The PDF navigator now honors the publication reading progression with support for right-to-left and horizontal scrolling. * The default (auto) reading progression for PDF is top-to-bottom, which is vertical scrolling. +* A new convenience utility `EdgeTapNavigation` to trigger page turns while tapping the screen edges. + * It takes into account the navigator reading progression to move into the right direction. + * Call it from the `VisualNavigator.Listener.onTap()` callback as demonstrated below: + ```kotlin + override fun onTap(point: PointF): Boolean { + val navigated = edgeTapNavigation.onTap(point, requireView()) + if (!navigated) { + // Fallback action, for example toggling the app bar. + } + return true + } + ``` ### Fixed diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/util/EdgeTapNavigation.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/util/EdgeTapNavigation.kt new file mode 100644 index 0000000000..bb2103a786 --- /dev/null +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/util/EdgeTapNavigation.kt @@ -0,0 +1,91 @@ +package org.readium.r2.navigator.util + +import android.graphics.PointF +import android.view.View +import org.readium.r2.navigator.VisualNavigator +import org.readium.r2.shared.publication.ReadingProgression + +/** + * Convenience utility to handle page turns when tapping the edge of the screen. + * + * Call [EdgeTapNavigation.onTap] from the [VisualNavigator.Listener.onTap] callback to turn pages + * automatically. + * + * @param navigator Navigator used to turn pages. + * @param minimumEdgeSize The minimum edge dimension triggering page turns, in pixels. + * @param edgeThresholdPercent The percentage of the viewport dimension used to compute the edge + * dimension. When null, minimumEdgeSize will be used instead. + * @param animatedTransition Indicates whether the page turns should be animated. + */ +class EdgeTapNavigation( + private val navigator: VisualNavigator, + private val minimumEdgeSize: Double = 200.0, + private val edgeThresholdPercent: Double? = 0.3, + private val animatedTransition: Boolean = false, +) { + private enum class Transition { + FORWARD, BACKWARD, NONE; + + fun reverse() = when (this) { + FORWARD -> BACKWARD + BACKWARD -> FORWARD + NONE -> NONE + } + } + + /** + * Handles a tap in the navigator viewport and returns whether it was successful. + * + * To be called from [VisualNavigator.Listener.onTap]. + * + * @param view Navigator view from which the point is relative. + */ + fun onTap(point: PointF, view: View): Boolean { + val horizontalEdgeSize by lazy { + if (edgeThresholdPercent != null) + (edgeThresholdPercent * view.width).coerceAtLeast(minimumEdgeSize) + else minimumEdgeSize + } + val leftRange by lazy { 0.0..horizontalEdgeSize } + val rightRange by lazy { (view.width - horizontalEdgeSize)..view.width.toDouble() } + + val verticalEdgeSize by lazy { + if (edgeThresholdPercent != null) + (edgeThresholdPercent * view.height).coerceAtLeast(minimumEdgeSize) + else minimumEdgeSize + } + val topRange by lazy { 0.0..verticalEdgeSize } + val bottomRange by lazy { (view.height - verticalEdgeSize)..view.height.toDouble() } + + val isHorizontal = navigator.readingProgression.isHorizontal ?: true + val isReverse = when (navigator.readingProgression) { + ReadingProgression.LTR, ReadingProgression.TTB, ReadingProgression.AUTO -> false + ReadingProgression.RTL, ReadingProgression.BTT -> true + } + + var transition: Transition = + if (isHorizontal) { + when { + rightRange.contains(point.x) -> Transition.FORWARD + leftRange.contains(point.x) -> Transition.BACKWARD + else -> Transition.NONE + } + } else { + when { + bottomRange.contains(point.y) -> Transition.FORWARD + topRange.contains(point.y) -> Transition.BACKWARD + else -> Transition.NONE + } + } + + if (isReverse) { + transition = transition.reverse() + } + + return when (transition) { + Transition.FORWARD -> navigator.goForward(animated = animatedTransition) + Transition.BACKWARD -> navigator.goBackward(animated = animatedTransition) + Transition.NONE -> false + } + } +} diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt index 9b46d88aeb..b9061c7857 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt @@ -274,11 +274,6 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene } } - override fun onTap(point: PointF): Boolean { - requireActivity().toggleSystemUi() - return true - } - private fun showSearchFragment() { childFragmentManager.commit { childFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)?.let { remove(it) } diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/ImageReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/ImageReaderFragment.kt index a097de3305..0c03d64c53 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/ImageReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/ImageReaderFragment.kt @@ -6,7 +6,6 @@ package org.readium.r2.testapp.reader -import android.graphics.PointF import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -17,7 +16,6 @@ import org.readium.r2.navigator.Navigator import org.readium.r2.navigator.image.ImageNavigatorFragment import org.readium.r2.shared.publication.Publication import org.readium.r2.testapp.R -import org.readium.r2.testapp.utils.toggleSystemUi class ImageReaderFragment : VisualReaderFragment(), ImageNavigatorFragment.Listener { @@ -48,19 +46,6 @@ class ImageReaderFragment : VisualReaderFragment(), ImageNavigatorFragment.Liste return view } - override fun onTap(point: PointF): Boolean { - val viewWidth = requireView().width - val leftRange = 0.0..(0.2 * viewWidth) - - when { - leftRange.contains(point.x) -> navigator.goBackward(animated = true) - leftRange.contains(viewWidth - point.x) -> navigator.goForward(animated = true) - else -> requireActivity().toggleSystemUi() - } - - return true - } - companion object { const val NAVIGATOR_FRAGMENT_TAG = "navigator" diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/PdfReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/PdfReaderFragment.kt index 36a510ec49..ccdd211f42 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/PdfReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/PdfReaderFragment.kt @@ -62,19 +62,6 @@ class PdfReaderFragment : VisualReaderFragment(), PdfNavigatorFragment.Listener requireActivity().finish() } - override fun onTap(point: PointF): Boolean { - val viewWidth = requireView().width - val leftRange = 0.0..(0.2 * viewWidth) - - when { - leftRange.contains(point.x) -> navigator.goBackward() - leftRange.contains(viewWidth - point.x) -> navigator.goForward() - else -> requireActivity().toggleSystemUi() - } - - return true - } - companion object { const val NAVIGATOR_FRAGMENT_TAG = "navigator" diff --git a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt index faa0aee026..839da26915 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt @@ -6,6 +6,7 @@ package org.readium.r2.testapp.reader +import android.graphics.PointF import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,17 +16,16 @@ import android.widget.FrameLayout import androidx.fragment.app.Fragment import org.readium.r2.navigator.DecorableNavigator import org.readium.r2.navigator.ExperimentalDecorator +import org.readium.r2.navigator.VisualNavigator +import org.readium.r2.navigator.util.EdgeTapNavigation import org.readium.r2.testapp.R import org.readium.r2.testapp.databinding.FragmentReaderBinding -import org.readium.r2.testapp.utils.clearPadding -import org.readium.r2.testapp.utils.hideSystemUi -import org.readium.r2.testapp.utils.padSystemUi -import org.readium.r2.testapp.utils.showSystemUi +import org.readium.r2.testapp.utils.* /* * Adds fullscreen support to the BaseReaderFragment */ -abstract class VisualReaderFragment : BaseReaderFragment() { +abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.Listener { private lateinit var navigatorFragment: Fragment @@ -75,4 +75,20 @@ abstract class VisualReaderFragment : BaseReaderFragment() { container.clearPadding() } } + + // VisualNavigator.Listener + + override fun onTap(point: PointF): Boolean { + val navigated = edgeTapNavigation.onTap(point, requireView()) + if (!navigated) { + requireActivity().toggleSystemUi() + } + return true + } + + private val edgeTapNavigation by lazy { + EdgeTapNavigation( + navigator = navigator as VisualNavigator + ) + } } \ No newline at end of file