diff options
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.kt | 69 |
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)) } } ) |
