@@ -12,6 +12,7 @@ package org.readium.r2.navigator.pager
1212import android.annotation.SuppressLint
1313import android.content.Context
1414import android.content.SharedPreferences
15+ import android.graphics.PointF
1516import android.os.Bundle
1617import android.util.Base64
1718import android.util.DisplayMetrics
@@ -23,10 +24,8 @@ import androidx.core.view.ViewCompat
2324import androidx.fragment.app.Fragment
2425import androidx.lifecycle.lifecycleScope
2526import androidx.webkit.WebViewClientCompat
26- import kotlinx.coroutines.delay
2727import kotlinx.coroutines.flow.collectLatest
2828import kotlinx.coroutines.launch
29- import org.readium.r2.navigator.Navigator
3029import org.readium.r2.navigator.R
3130import org.readium.r2.navigator.R2BasicWebView
3231import org.readium.r2.navigator.R2WebView
@@ -49,6 +48,9 @@ class R2EpubPageFragment : Fragment() {
4948 internal val link: Link ?
5049 get() = requireArguments().getParcelable(" link" )
5150
51+ private val positionCount: Long
52+ get() = requireArguments().getLong(" positionCount" )
53+
5254 var webView: R2WebView ? = null
5355 private set
5456
@@ -58,6 +60,8 @@ class R2EpubPageFragment : Fragment() {
5860 private var _binding : ViewpagerFragmentEpubBinding ? = null
5961 private val binding get() = _binding !!
6062
63+ private var isLoading: Boolean = false
64+
6165 @SuppressLint(" SetJavaScriptEnabled" )
6266 override fun onCreateView (inflater : LayoutInflater , container : ViewGroup ? , savedInstanceState : Bundle ? ): View ? {
6367 val navigatorFragment = parentFragment as EpubNavigatorFragment
@@ -68,6 +72,7 @@ class R2EpubPageFragment : Fragment() {
6872 val webView = binding.webView
6973 this .webView = webView
7074
75+ webView.visibility = View .INVISIBLE
7176 webView.navigator = navigatorFragment
7277 webView.listener = navigatorFragment.webViewListener
7378 webView.preferences = preferences
@@ -127,71 +132,13 @@ class R2EpubPageFragment : Fragment() {
127132
128133 webView.listener.onResourceLoaded(link, webView, url)
129134
130- val epubNavigator = (webView.navigator as ? EpubNavigatorFragment )
131- val currentFragment: R2EpubPageFragment ? =
132- (epubNavigator?.resourcePager?.adapter as ? R2PagerAdapter )?.getCurrentFragment() as ? R2EpubPageFragment
133-
134- if (currentFragment != null && this @R2EpubPageFragment.tag == currentFragment.tag) {
135- val locator = epubNavigator.pendingLocator
136- epubNavigator.pendingLocator = null
137- var locations = locator?.locations
138-
139- // TODO this seems to be needed, will need to test more
140- if (url != null && url.indexOf(" #" ) > 0 ) {
141- val id = url.substringAfterLast(" #" )
142- locations = Locator .Locations (fragments = listOf (id))
135+ // To make sure the page is properly laid out before jumping to the target locator,
136+ // we execute a dummy JavaScript and wait for the callback result.
137+ webView.evaluateJavascript(" true" ) {
138+ viewLifecycleOwner.lifecycleScope.launchWhenCreated {
139+ onLoadPage()
143140 }
144-
145- val currentWebView = currentFragment.webView
146- if (currentWebView != null && locations != null ) {
147-
148- lifecycleScope.launchWhenStarted {
149- // FIXME: We need a better way to wait, because if the value is too low it fails
150- delay(200 )
151-
152- val text = locator?.text
153- if (text?.highlight != null ) {
154- if (currentWebView.scrollToText(text)) {
155- return @launchWhenStarted
156- }
157-
158- // The delay is necessary before falling back on the other
159- // locations, because scrollToText() is moving the scroll position
160- // while looking for the text snippet.
161- delay(100 )
162- }
163-
164- val htmlId = locations.htmlId
165- if (htmlId != null && currentWebView.scrollToId(htmlId)) {
166- return @launchWhenStarted
167- }
168-
169- var progression = locations.progression
170- if (progression != null ) {
171- // We need to reverse the progression with RTL because the Web View
172- // always scrolls from left to right, no matter the reading direction.
173- progression =
174- if (webView.scrollMode || navigatorFragment.readingProgression == ReadingProgression .LTR ) progression
175- else 1 - progression
176-
177- if (webView.scrollMode) {
178- currentWebView.scrollToPosition(progression)
179-
180- } else {
181- // Figure out the target web view "page" from the requested
182- // progression.
183- var item = (progression * currentWebView.numPages).roundToInt()
184- if (navigatorFragment.readingProgression == ReadingProgression .RTL && item > 0 ) {
185- item - = 1
186- }
187- currentWebView.setCurrentItem(item, false )
188- }
189- }
190- }
191- }
192-
193141 }
194- webView.listener.onPageLoaded()
195142 }
196143
197144 // prevent favicon.ico to be loaded, this was causing NullPointerException in NanoHttp
@@ -237,10 +184,19 @@ class R2EpubPageFragment : Fragment() {
237184 false
238185 }
239186
240- resourceUrl?.let { webView.loadUrl(it) }
187+ resourceUrl?.let {
188+ isLoading = true
189+ webView.loadUrl(it)
190+ }
241191
242192 setupPadding()
243193
194+ // Forward a tap event when the web view is not ready to propagate the taps. This allows
195+ // to toggle a navigation UI while a page is loading, for example.
196+ binding.root.setOnClickListenerWithPoint { _, point ->
197+ webView.listener.onTap(point)
198+ }
199+
244200 return containerView
245201 }
246202
@@ -312,15 +268,96 @@ class R2EpubPageFragment : Fragment() {
312268 internal val paddingTop: Int get() = containerView.paddingTop
313269 internal val paddingBottom: Int get() = containerView.paddingBottom
314270
271+ private val isCurrentResource: Boolean get() {
272+ val epubNavigator = webView?.navigator as ? EpubNavigatorFragment ? : return false
273+ val currentFragment = (epubNavigator.resourcePager.adapter as ? R2PagerAdapter )?.getCurrentFragment() as ? R2EpubPageFragment ? : return false
274+ return tag == currentFragment.tag
275+ }
276+
277+ private suspend fun onLoadPage () {
278+ if (! isLoading) return
279+ isLoading = false
280+
281+ val webView = requireNotNull(webView)
282+ webView.visibility = View .VISIBLE
283+
284+ if (isCurrentResource) {
285+ val epubNavigator = requireNotNull(webView.navigator as ? EpubNavigatorFragment )
286+ val locator = epubNavigator.pendingLocator
287+ epubNavigator.pendingLocator = null
288+ if (locator != null ) {
289+ loadLocator(locator)
290+ }
291+
292+ webView.listener.onPageLoaded()
293+ }
294+ }
295+
296+ private suspend fun loadLocator (locator : Locator ) {
297+ val webView = requireNotNull(webView)
298+ val epubNavigator = requireNotNull(webView.navigator as ? EpubNavigatorFragment )
299+
300+ val text = locator.text
301+ if (text.highlight != null ) {
302+ if (webView.scrollToText(text)) {
303+ return
304+ }
305+ }
306+
307+ val htmlId = locator.locations.htmlId
308+ if (htmlId != null && webView.scrollToId(htmlId)) {
309+ return
310+ }
311+
312+ var progression = locator.locations.progression
313+ if (progression != null ) {
314+ // We need to reverse the progression with RTL because the Web View
315+ // always scrolls from left to right, no matter the reading direction.
316+ progression =
317+ if (webView.scrollMode || epubNavigator.readingProgression == ReadingProgression .LTR ) progression
318+ else 1 - progression
319+
320+ if (webView.scrollMode) {
321+ webView.scrollToPosition(progression)
322+
323+ } else {
324+ // Figure out the target web view "page" from the requested
325+ // progression.
326+ var item = (progression * webView.numPages).roundToInt()
327+ if (epubNavigator.readingProgression == ReadingProgression .RTL && item > 0 ) {
328+ item - = 1
329+ }
330+ webView.setCurrentItem(item, false )
331+ }
332+ }
333+ }
334+
315335 companion object {
316- fun newInstance (url : String , link : Link ? = null): R2EpubPageFragment =
336+ fun newInstance (url : String , link : Link ? = null, positionCount : Int = 0 ): R2EpubPageFragment =
317337 R2EpubPageFragment ().apply {
318338 arguments = Bundle ().apply {
319339 putString(" url" , url)
320340 putParcelable(" link" , link)
341+ putLong(" positionCount" , positionCount.toLong())
321342 }
322343 }
323344 }
324345}
325346
347+ /* *
348+ * Same as setOnClickListener, but will also report the tap point in the view.
349+ */
350+ private fun View.setOnClickListenerWithPoint (action : (View , PointF ) -> Unit ) {
351+ var point = PointF ()
326352
353+ setOnTouchListener { v, event ->
354+ if (event.action == MotionEvent .ACTION_DOWN ) {
355+ point = PointF (event.x, event.y)
356+ }
357+ false
358+ }
359+
360+ setOnClickListener {
361+ action(it, point)
362+ }
363+ }
0 commit comments