summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt')
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt69
1 files changed, 52 insertions, 17 deletions
diff --git a/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt b/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
index 9a11dd3..6d96e79 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
@@ -24,7 +24,9 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import org.terst.feedreader.data.DEFAULT_READER_FONT_SIZE_PX
+import org.terst.feedreader.data.UNZOOMED_TEXT_ZOOM_PERCENT
import org.terst.feedreader.data.pinchZoomFontSizePx
+import org.terst.feedreader.data.readerTextZoomPercent
// In-memory only (process lifetime, not persisted to disk) -- scoped to "don't lose my
// place if Next/Previous takes me away and back," not to surviving an app restart. A
@@ -99,16 +101,25 @@ private fun extractYouTubeVideoId(uri: Uri): String? {
* exactly as before. ScaleGestureDetector only calls back once a second pointer is down,
* so a single-finger drag never triggers any of this.
*
- * currentFontSizePx is read fresh (not captured once) when a gesture ends, since the
- * WebView this attaches to is created once but can outlive a Settings-screen-driven font
- * size change made while this same article is still open.
+ * currentFontSizePx is read fresh (not captured once) at the START of each gesture, since
+ * the WebView this attaches to is created once but can outlive a Settings-screen-driven
+ * font size change made while this same article is still open. Everything during the
+ * gesture is measured against that one starting size, so a pinch is judged against where
+ * it began rather than against a size it's already changed.
*
- * During the gesture the WebView is visually scaled live (View.scaleX/scaleY) for
- * immediate feedback -- cheap, and independent of the WebView's own content layout.
- * At gesture end the scale resets to 1x and onFontSizeChanged reports the new,
- * clamped, rounded font size for the caller to persist; ReaderScreen's existing
- * content-vs-fontSizePx reload logic (see ReaderScreen.ReaderContent) then re-renders
- * the actual HTML at that size.
+ * The text reflows live, as the fingers move: each step sets WebSettings.textZoom, which
+ * re-lays-out the text at the new size right away -- no reload, no JavaScript, and
+ * images/padding stay exactly where they are. An earlier version instead scaled the whole
+ * WebView (View.scaleX/scaleY) during the gesture and snapped back to 1x on release, which
+ * zoomed the canvas like a photo (blurring text, magnifying the page's own margins) and
+ * then visibly jumped when the real font size took over. Nothing is scaled now, so there's
+ * nothing to snap back from.
+ *
+ * At gesture end onFontSizeChanged reports the new, clamped, rounded font size for the
+ * caller to persist; ReaderScreen's content-vs-fontSizePx reload logic then re-renders the
+ * HTML at that size via loadReaderHtml, which is also what returns textZoom to 100% -- the
+ * live zoom stays applied until the reload it hands over to is on screen, so the two never
+ * both apply and there's no gap where the text flicks back to its old size.
*/
data class ReaderWebViewCallbacks(
val onOpenLink: (String) -> Unit,
@@ -121,8 +132,30 @@ data class ReaderWebViewCallbacks(
val onFontSizeChanged: (Int) -> Unit = {}
)
-fun newReaderWebView(context: Context, callbacks: ReaderWebViewCallbacks): WebView =
- WebView(context).apply {
+/**
+ * The reader's WebView, which knows what font size the HTML it's showing was written at.
+ *
+ * That's the whole reason it's a class rather than a plain WebView: a live pinch lifts
+ * WebSettings.textZoom off 100% and leaves it there until the reload at the new font size
+ * arrives (see the gesture notes above). Anything that loads HTML into this WebView must put
+ * textZoom back, or the lifted zoom multiplies the new, already-bigger CSS size -- so loading
+ * goes through [loadReaderHtml], which does both together and can't be forgotten the way a
+ * separate "and also reset the zoom" line at each call site could.
+ */
+class ReaderWebView(context: Context) : WebView(context) {
+ /** The `font-size` the currently-loaded HTML was written with (see wrapReaderHtml). */
+ var renderedFontSizePx: Int = DEFAULT_READER_FONT_SIZE_PX
+ private set
+
+ fun loadReaderHtml(baseUrl: String?, html: String, fontSizePx: Int) {
+ renderedFontSizePx = fontSizePx
+ settings.textZoom = UNZOOMED_TEXT_ZOOM_PERCENT
+ loadDataWithBaseURL(baseUrl, html, "text/html", "UTF-8", null)
+ }
+}
+
+fun newReaderWebView(context: Context, callbacks: ReaderWebViewCallbacks): ReaderWebView =
+ ReaderWebView(context).apply {
settings.javaScriptEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
isVerticalScrollBarEnabled = false
@@ -135,27 +168,29 @@ fun newReaderWebView(context: Context, callbacks: ReaderWebViewCallbacks): WebVi
setOnScrollChangeListener { _, _, scrollY, _, _ -> callbacks.onScrollChanged(scrollY) }
var pinchScale = 1f
+ var pinchBaseFontSizePx = DEFAULT_READER_FONT_SIZE_PX
val webView = this
val scaleDetector = ScaleGestureDetector(
context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
pinchScale = 1f
+ pinchBaseFontSizePx = callbacks.currentFontSizePx()
return true
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
pinchScale *= detector.scaleFactor
- webView.scaleX = pinchScale
- webView.scaleY = pinchScale
+ // Steps in whole pixels of font size, not in raw gesture scale, so a slow
+ // pinch relayouts a handful of times over its whole range rather than on
+ // every touch-move frame -- and lands on exactly the size it will persist.
+ val targetPx = pinchZoomFontSizePx(pinchBaseFontSizePx, pinchScale)
+ webView.settings.textZoom = readerTextZoomPercent(webView.renderedFontSizePx, targetPx)
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
- val newSizePx = pinchZoomFontSizePx(callbacks.currentFontSizePx(), pinchScale)
- webView.scaleX = 1f
- webView.scaleY = 1f
- callbacks.onFontSizeChanged(newSizePx)
+ callbacks.onFontSizeChanged(pinchZoomFontSizePx(pinchBaseFontSizePx, pinchScale))
}
}
)