diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-23 00:47:56 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-23 00:47:56 +0000 |
| commit | c8b6c5344c7194e2ef3336446919b66714062560 (patch) | |
| tree | 5cd4c30244fb02c6128cc78bccdc50ba52e47fd2 /app/src/main/java/org/terst | |
| parent | 5f3850ab73903010269df6009204a96f9d3e68ac (diff) | |
Restore scroll position within an article on Next/Previous
Next/Previous replaces this screen's own back-stack entry with a fresh one
for the neighboring article, which recreates the WebView from scratch --
going back to an article you'd been partway through always landed you back
at the top. Tracks each article's scroll offset in an in-memory,
process-lifetime map (ReaderScrollPositions) as the WebView scrolls, and
restores it once the freshly-loaded page finishes laying out.
Diffstat (limited to 'app/src/main/java/org/terst')
| -rw-r--r-- | app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt | 7 | ||||
| -rw-r--r-- | app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt | 27 |
2 files changed, 32 insertions, 2 deletions
diff --git a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt index 4c74e5a..2bdaf64 100644 --- a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt +++ b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt @@ -519,7 +519,12 @@ private fun ReaderContent( ctx, onOpenLink, onLongPressLink = { longPressedLink = it }, - onYouTubeVideo = { youtubeVideoId = it } + onYouTubeVideo = { youtubeVideoId = it }, + onScrollChanged = { scrollY -> ReaderScrollPositions.set(articleId, scrollY) }, + // The page's real content height isn't known until layout finishes + // -- scrolling to a saved offset any earlier would just clamp back + // to 0 on a page that hasn't grown tall enough yet. + onPageFinished = { webView -> webView.scrollTo(0, ReaderScrollPositions.get(articleId)) } ).apply { loadDataWithBaseURL(baseUrl, wrapReaderHtml(content, article?.title, article?.publishedAt, article?.imageUrl), "text/html", "UTF-8", null) loadedContent.value = content 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 f0873c8..31fcb02 100644 --- a/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt +++ b/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt @@ -23,6 +23,18 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +// 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 +// process-wide singleton rather than screen-scoped state so it survives an Activity +// recreation (rotation) too, not just Compose recomposition within the same screen instance. +object ReaderScrollPositions { + private val positions = mutableMapOf<String, Int>() + fun get(articleId: String): Int = positions[articleId] ?: 0 + fun set(articleId: String, scrollY: Int) { + positions[articleId] = scrollY + } +} + private fun isYouTubeWatchUrl(uri: Uri): Boolean { val host = uri.host?.lowercase() ?: return false return host == "youtu.be" || host.endsWith(".youtube.com") || host == "youtube.com" @@ -68,14 +80,27 @@ fun newReaderWebView( context: Context, onOpenLink: (String) -> Unit, onLongPressLink: (String) -> Unit = {}, - onYouTubeVideo: (String) -> Unit = {} + onYouTubeVideo: (String) -> Unit = {}, + onScrollChanged: (Int) -> Unit = {}, + onPageFinished: (WebView) -> Unit = {} ): WebView = WebView(context).apply { settings.javaScriptEnabled = false overScrollMode = View.OVER_SCROLL_NEVER isVerticalScrollBarEnabled = false isHorizontalScrollBarEnabled = false + // Reports the WebView's own scroll offset as the user reads, so a caller (ReaderScreen) + // can remember how far into an article they'd gotten and restore it -- e.g. after + // Next/Previous replaces this screen's back-stack entry with a fresh one for the same + // article, which recreates the WebView from scratch and would otherwise always land + // back at the top. + setOnScrollChangeListener { _, _, scrollY, _, _ -> onScrollChanged(scrollY) } webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + onPageFinished(view) + } + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { // A subframe (an <iframe> in the article body, e.g. a non-YouTube oEmbed // like Twitter/Instagram, or an ad) fires this same callback the instant |
