@@ -12,17 +12,12 @@ import android.graphics.PointF
12
12
import android.graphics.Rect
13
13
import android.graphics.RectF
14
14
import android.os.Build
15
- import android.text.Html
16
15
import android.util.AttributeSet
17
16
import android.view.*
18
17
import android.webkit.URLUtil
19
18
import android.webkit.WebResourceRequest
20
19
import android.webkit.WebResourceResponse
21
20
import android.webkit.WebView
22
- import android.widget.ImageButton
23
- import android.widget.ListPopupWindow
24
- import android.widget.PopupWindow
25
- import android.widget.TextView
26
21
import androidx.annotation.RequiresApi
27
22
import kotlin.coroutines.resume
28
23
import kotlin.coroutines.suspendCoroutine
@@ -46,6 +41,7 @@ import org.readium.r2.shared.extensions.tryOrLog
46
41
import org.readium.r2.shared.extensions.tryOrNull
47
42
import org.readium.r2.shared.publication.Link
48
43
import org.readium.r2.shared.publication.Locator
44
+ import org.readium.r2.shared.util.AbsoluteUrl
49
45
import org.readium.r2.shared.util.Url
50
46
import org.readium.r2.shared.util.data.decodeString
51
47
import org.readium.r2.shared.util.flatMap
@@ -87,6 +83,9 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
87
83
@InternalReadiumApi
88
84
fun shouldInterceptRequest (webView : WebView , request : WebResourceRequest ): WebResourceResponse ? = null
89
85
86
+ @InternalReadiumApi
87
+ fun shouldFollowFootnoteLink (url : AbsoluteUrl , context : HyperlinkNavigator .FootnoteContext ): Boolean
88
+
90
89
@InternalReadiumApi
91
90
fun resourceAtUrl (url : Url ): Resource ? = null
92
91
@@ -115,7 +114,7 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
115
114
var listener: Listener ? = null
116
115
internal var preferences: SharedPreferences ? = null
117
116
118
- var resourceUrl: Url ? = null
117
+ var resourceUrl: AbsoluteUrl ? = null
119
118
120
119
internal val scrollModeFlow = MutableStateFlow (false )
121
120
@@ -128,6 +127,12 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
128
127
129
128
private val uiScope = CoroutineScope (Dispatchers .Main )
130
129
130
+ /*
131
+ * Url already handled by listener.shouldFollowFootnoteLink,
132
+ * Tries to ignore the matching shouldOverrideUrlLoading call.
133
+ */
134
+ private var urlNotToOverrideLoading: AbsoluteUrl ? = null
135
+
131
136
init {
132
137
setWebContentsDebuggingEnabled(BuildConfig .DEBUG )
133
138
}
@@ -277,8 +282,6 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
277
282
return false
278
283
}
279
284
280
- // FIXME: Let the app handle footnotes.
281
-
282
285
// We ignore taps on interactive element, unless it's an element we handle ourselves such as
283
286
// pop-up footnotes.
284
287
if (event.interactiveElement != null ) {
@@ -344,11 +347,13 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
344
347
345
348
val id = href.fragment ? : return false
346
349
347
- val absoluteUrl = resourceUrl.resolve(href).removeFragment()
350
+ val absoluteUrl = resourceUrl.resolve(href)
351
+
352
+ val absoluteUrlWithoutFragment = absoluteUrl.removeFragment()
348
353
349
354
val aside = runBlocking {
350
355
tryOrLog {
351
- listener?.resourceAtUrl(absoluteUrl )
356
+ listener?.resourceAtUrl(absoluteUrlWithoutFragment )
352
357
?.use { res ->
353
358
res.read()
354
359
.flatMap { it.decodeString() }
@@ -358,50 +363,22 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
358
363
?.select(" #$id " )
359
364
?.first()?.html()
360
365
}
361
- } ? : return false
366
+ }?.takeIf { it.isNotBlank() }
367
+ ? : return false
362
368
363
369
val safe = Jsoup .clean(aside, Safelist .relaxed())
364
-
365
- // Initialize a new instance of LayoutInflater service
366
- val inflater = context.getSystemService(Context .LAYOUT_INFLATER_SERVICE ) as LayoutInflater
367
-
368
- // Inflate the custom layout/view
369
- val customView = inflater.inflate(R .layout.readium_navigator_popup_footnote, null )
370
-
371
- // Initialize a new instance of popup window
372
- val mPopupWindow = PopupWindow (
373
- customView,
374
- ListPopupWindow .WRAP_CONTENT ,
375
- ListPopupWindow .WRAP_CONTENT
370
+ val context = HyperlinkNavigator .FootnoteContext (
371
+ noteContent = safe
376
372
)
377
- mPopupWindow.isOutsideTouchable = true
378
- mPopupWindow.isFocusable = true
379
373
380
- // Set an elevation value for popup window
381
- // Call requires API level 21
382
- mPopupWindow.elevation = 5.0f
374
+ val shouldFollowLink = listener?.shouldFollowFootnoteLink(absoluteUrl, context) ? : true
383
375
384
- val textView = customView.findViewById(R .id.footnote) as TextView
385
- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
386
- textView.text = Html .fromHtml(safe, Html .FROM_HTML_MODE_COMPACT )
387
- } else {
388
- @Suppress(" DEPRECATION" )
389
- textView.text = Html .fromHtml(safe)
376
+ if (shouldFollowLink) {
377
+ urlNotToOverrideLoading = absoluteUrl
390
378
}
391
379
392
- // Get a reference for the custom view close button
393
- val closeButton = customView.findViewById(R .id.ib_close) as ImageButton
394
-
395
- // Set a click listener for the popup window close button
396
- closeButton.setOnClickListener {
397
- // Dismiss the popup window
398
- mPopupWindow.dismiss()
399
- }
400
-
401
- // Finally, show the popup window at the center location of root relative layout
402
- mPopupWindow.showAtLocation(this , Gravity .CENTER , 0 , 0 )
403
-
404
- return true
380
+ // Consume event if the link should not be followed.
381
+ return ! shouldFollowLink
405
382
}
406
383
407
384
@android.webkit.JavascriptInterface
@@ -596,9 +573,15 @@ internal open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebV
596
573
}
597
574
598
575
internal fun shouldOverrideUrlLoading (request : WebResourceRequest ): Boolean {
599
- if (resourceUrl == request.url.toUrl()) return false
576
+ val requestUrl = request.url.toUrl() ? : return false
600
577
601
- return listener?.shouldOverrideUrlLoading(this , request) ? : false
578
+ // FIXME: I doubt this can work well. hasGesture considers itself unreliable.
579
+ return if (urlNotToOverrideLoading == requestUrl && request.hasGesture()) {
580
+ urlNotToOverrideLoading = null
581
+ false
582
+ } else {
583
+ listener?.shouldOverrideUrlLoading(this , request) ? : false
584
+ }
602
585
}
603
586
604
587
internal fun shouldInterceptRequest (webView : WebView , request : WebResourceRequest ): WebResourceResponse ? {
0 commit comments