Skip to content

Commit c801648

Browse files
authored
Add EdgeTapNavigation to trigger page turns on edge taps (#55)
1 parent b5b2b73 commit c801648

File tree

6 files changed

+124
-38
lines changed

6 files changed

+124
-38
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ All notable changes to this project will be documented in this file. Take a look
1212

1313
* The PDF navigator now honors the publication reading progression with support for right-to-left and horizontal scrolling.
1414
* The default (auto) reading progression for PDF is top-to-bottom, which is vertical scrolling.
15+
* A new convenience utility `EdgeTapNavigation` to trigger page turns while tapping the screen edges.
16+
* It takes into account the navigator reading progression to move into the right direction.
17+
* Call it from the `VisualNavigator.Listener.onTap()` callback as demonstrated below:
18+
```kotlin
19+
override fun onTap(point: PointF): Boolean {
20+
val navigated = edgeTapNavigation.onTap(point, requireView())
21+
if (!navigated) {
22+
// Fallback action, for example toggling the app bar.
23+
}
24+
return true
25+
}
26+
```
1527

1628
### Fixed
1729

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.readium.r2.navigator.util
2+
3+
import android.graphics.PointF
4+
import android.view.View
5+
import org.readium.r2.navigator.VisualNavigator
6+
import org.readium.r2.shared.publication.ReadingProgression
7+
8+
/**
9+
* Convenience utility to handle page turns when tapping the edge of the screen.
10+
*
11+
* Call [EdgeTapNavigation.onTap] from the [VisualNavigator.Listener.onTap] callback to turn pages
12+
* automatically.
13+
*
14+
* @param navigator Navigator used to turn pages.
15+
* @param minimumEdgeSize The minimum edge dimension triggering page turns, in pixels.
16+
* @param edgeThresholdPercent The percentage of the viewport dimension used to compute the edge
17+
* dimension. When null, minimumEdgeSize will be used instead.
18+
* @param animatedTransition Indicates whether the page turns should be animated.
19+
*/
20+
class EdgeTapNavigation(
21+
private val navigator: VisualNavigator,
22+
private val minimumEdgeSize: Double = 200.0,
23+
private val edgeThresholdPercent: Double? = 0.3,
24+
private val animatedTransition: Boolean = false,
25+
) {
26+
private enum class Transition {
27+
FORWARD, BACKWARD, NONE;
28+
29+
fun reverse() = when (this) {
30+
FORWARD -> BACKWARD
31+
BACKWARD -> FORWARD
32+
NONE -> NONE
33+
}
34+
}
35+
36+
/**
37+
* Handles a tap in the navigator viewport and returns whether it was successful.
38+
*
39+
* To be called from [VisualNavigator.Listener.onTap].
40+
*
41+
* @param view Navigator view from which the point is relative.
42+
*/
43+
fun onTap(point: PointF, view: View): Boolean {
44+
val horizontalEdgeSize by lazy {
45+
if (edgeThresholdPercent != null)
46+
(edgeThresholdPercent * view.width).coerceAtLeast(minimumEdgeSize)
47+
else minimumEdgeSize
48+
}
49+
val leftRange by lazy { 0.0..horizontalEdgeSize }
50+
val rightRange by lazy { (view.width - horizontalEdgeSize)..view.width.toDouble() }
51+
52+
val verticalEdgeSize by lazy {
53+
if (edgeThresholdPercent != null)
54+
(edgeThresholdPercent * view.height).coerceAtLeast(minimumEdgeSize)
55+
else minimumEdgeSize
56+
}
57+
val topRange by lazy { 0.0..verticalEdgeSize }
58+
val bottomRange by lazy { (view.height - verticalEdgeSize)..view.height.toDouble() }
59+
60+
val isHorizontal = navigator.readingProgression.isHorizontal ?: true
61+
val isReverse = when (navigator.readingProgression) {
62+
ReadingProgression.LTR, ReadingProgression.TTB, ReadingProgression.AUTO -> false
63+
ReadingProgression.RTL, ReadingProgression.BTT -> true
64+
}
65+
66+
var transition: Transition =
67+
if (isHorizontal) {
68+
when {
69+
rightRange.contains(point.x) -> Transition.FORWARD
70+
leftRange.contains(point.x) -> Transition.BACKWARD
71+
else -> Transition.NONE
72+
}
73+
} else {
74+
when {
75+
bottomRange.contains(point.y) -> Transition.FORWARD
76+
topRange.contains(point.y) -> Transition.BACKWARD
77+
else -> Transition.NONE
78+
}
79+
}
80+
81+
if (isReverse) {
82+
transition = transition.reverse()
83+
}
84+
85+
return when (transition) {
86+
Transition.FORWARD -> navigator.goForward(animated = animatedTransition)
87+
Transition.BACKWARD -> navigator.goBackward(animated = animatedTransition)
88+
Transition.NONE -> false
89+
}
90+
}
91+
}

test-app/src/main/java/org/readium/r2/testapp/reader/EpubReaderFragment.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,6 @@ class EpubReaderFragment : VisualReaderFragment(), EpubNavigatorFragment.Listene
274274
}
275275
}
276276

277-
override fun onTap(point: PointF): Boolean {
278-
requireActivity().toggleSystemUi()
279-
return true
280-
}
281-
282277
private fun showSearchFragment() {
283278
childFragmentManager.commit {
284279
childFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)?.let { remove(it) }

test-app/src/main/java/org/readium/r2/testapp/reader/ImageReaderFragment.kt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
package org.readium.r2.testapp.reader
88

9-
import android.graphics.PointF
109
import android.os.Bundle
1110
import android.view.LayoutInflater
1211
import android.view.View
@@ -17,7 +16,6 @@ import org.readium.r2.navigator.Navigator
1716
import org.readium.r2.navigator.image.ImageNavigatorFragment
1817
import org.readium.r2.shared.publication.Publication
1918
import org.readium.r2.testapp.R
20-
import org.readium.r2.testapp.utils.toggleSystemUi
2119

2220
class ImageReaderFragment : VisualReaderFragment(), ImageNavigatorFragment.Listener {
2321

@@ -48,19 +46,6 @@ class ImageReaderFragment : VisualReaderFragment(), ImageNavigatorFragment.Liste
4846
return view
4947
}
5048

51-
override fun onTap(point: PointF): Boolean {
52-
val viewWidth = requireView().width
53-
val leftRange = 0.0..(0.2 * viewWidth)
54-
55-
when {
56-
leftRange.contains(point.x) -> navigator.goBackward(animated = true)
57-
leftRange.contains(viewWidth - point.x) -> navigator.goForward(animated = true)
58-
else -> requireActivity().toggleSystemUi()
59-
}
60-
61-
return true
62-
}
63-
6449
companion object {
6550

6651
const val NAVIGATOR_FRAGMENT_TAG = "navigator"

test-app/src/main/java/org/readium/r2/testapp/reader/PdfReaderFragment.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,6 @@ class PdfReaderFragment : VisualReaderFragment(), PdfNavigatorFragment.Listener
6262
requireActivity().finish()
6363
}
6464

65-
override fun onTap(point: PointF): Boolean {
66-
val viewWidth = requireView().width
67-
val leftRange = 0.0..(0.2 * viewWidth)
68-
69-
when {
70-
leftRange.contains(point.x) -> navigator.goBackward()
71-
leftRange.contains(viewWidth - point.x) -> navigator.goForward()
72-
else -> requireActivity().toggleSystemUi()
73-
}
74-
75-
return true
76-
}
77-
7865
companion object {
7966

8067
const val NAVIGATOR_FRAGMENT_TAG = "navigator"

test-app/src/main/java/org/readium/r2/testapp/reader/VisualReaderFragment.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package org.readium.r2.testapp.reader
88

9+
import android.graphics.PointF
910
import android.os.Bundle
1011
import android.view.LayoutInflater
1112
import android.view.View
@@ -15,17 +16,16 @@ import android.widget.FrameLayout
1516
import androidx.fragment.app.Fragment
1617
import org.readium.r2.navigator.DecorableNavigator
1718
import org.readium.r2.navigator.ExperimentalDecorator
19+
import org.readium.r2.navigator.VisualNavigator
20+
import org.readium.r2.navigator.util.EdgeTapNavigation
1821
import org.readium.r2.testapp.R
1922
import org.readium.r2.testapp.databinding.FragmentReaderBinding
20-
import org.readium.r2.testapp.utils.clearPadding
21-
import org.readium.r2.testapp.utils.hideSystemUi
22-
import org.readium.r2.testapp.utils.padSystemUi
23-
import org.readium.r2.testapp.utils.showSystemUi
23+
import org.readium.r2.testapp.utils.*
2424

2525
/*
2626
* Adds fullscreen support to the BaseReaderFragment
2727
*/
28-
abstract class VisualReaderFragment : BaseReaderFragment() {
28+
abstract class VisualReaderFragment : BaseReaderFragment(), VisualNavigator.Listener {
2929

3030
private lateinit var navigatorFragment: Fragment
3131

@@ -75,4 +75,20 @@ abstract class VisualReaderFragment : BaseReaderFragment() {
7575
container.clearPadding()
7676
}
7777
}
78+
79+
// VisualNavigator.Listener
80+
81+
override fun onTap(point: PointF): Boolean {
82+
val navigated = edgeTapNavigation.onTap(point, requireView())
83+
if (!navigated) {
84+
requireActivity().toggleSystemUi()
85+
}
86+
return true
87+
}
88+
89+
private val edgeTapNavigation by lazy {
90+
EdgeTapNavigation(
91+
navigator = navigator as VisualNavigator
92+
)
93+
}
7894
}

0 commit comments

Comments
 (0)