summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-22 18:56:47 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-22 18:56:47 +0000
commitbedf8a47a8b0601f4e3c253e4d2a7325c6ccbfc6 (patch)
treeddfaebfc7d7b2307f7d8658bbfa74cefce27b377 /app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
parente804e7a77a6cac048d5f6edeac0a339ca89e4d40 (diff)
Fix accidental page-swipe while scrolling; bring linked-article reader to parity
- ReaderScreen: HorizontalPager's full-surface drag detection was treating ordinary vertical scrolling inside the embedded WebView (a native View, which doesn't arbitrate with Compose's gesture system the way a pure-Compose scrollable child would) as an attempted horizontal page-turn on anything less than a perfectly vertical swipe -- i.e. most real thumb scrolling. Tried gating it to edge-zone-only drags first, but that collides with Android's own edge-swipe-back gesture on gesture-nav devices (reproduced live: an edge swipe triggered system back, not the pager). Landed on disabling the Pager's built-in gesture and driving it from a custom detector that works anywhere on screen but only commits to a page turn once a drag is clearly horizontal-dominant (>2x the vertical movement) -- ordinary scrolling, even sloppy/diagonal, is left completely unconsumed for the WebView. Verified live: a deliberately diagonal mid-screen scroll now stays on the same article; a decisive horizontal swipe still changes pages. - LinkReaderScreen: was missing the bookmark/Stash/reader-toggle/menu actions entirely -- reported after tapping a link left "all my icons... gone." Brought to parity with the main article reader: save locally and save to Stash now lazily create a standalone ArticleEntity (feedId=null, id = hash of the URL, same scheme ShareReceiverActivity uses) on first use; reader-mode toggle falls back to the live page (JS on) instead of always forcing extraction; same login/find-another-source menu. Verified live: all 5 icons present once extraction finishes, bookmarking a linked article correctly creates a standalone DB row.
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt')
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt77
1 files changed, 70 insertions, 7 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 8c2f64e..9b53f8c 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
@@ -3,6 +3,7 @@ package org.terst.feedreader.ui
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -40,8 +41,11 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -54,6 +58,7 @@ import org.terst.feedreader.readability.ReaderExtractor
import org.terst.feedreader.stash.StashClient
import org.terst.feedreader.stash.StashSaveResult
import java.net.URLEncoder
+import kotlin.math.abs
// Below this length (of the plain feed-provided HTML), assume it's a teaser/summary,
// not the real article body, and auto-fall-back to on-device Readability extraction.
@@ -100,14 +105,72 @@ fun ReaderScreen(
val safeIds = if (ids.contains(initialArticleId)) ids else listOf(initialArticleId)
val initialIndex = safeIds.indexOf(initialArticleId).coerceAtLeast(0)
val pagerState = rememberPagerState(initialPage = initialIndex) { safeIds.size }
+ val scope = rememberCoroutineScope()
+ val directionLockPx = with(LocalDensity.current) { 16.dp.toPx() }
+ val swipeThresholdPx = with(LocalDensity.current) { 80.dp.toPx() }
- HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page ->
- ReaderContent(
- articleId = safeIds[page],
- onBack = onBack,
- onOpenLink = onOpenLink,
- onOpenBrowser = onOpenBrowser
- )
+ // HorizontalPager's own full-surface drag detection was grabbing ordinary vertical
+ // scrolling inside the embedded WebView (a native View, which doesn't arbitrate with
+ // Compose's gesture system the way a pure-Compose scrollable child would) as an
+ // attempted horizontal page-turn on anything less than a perfectly vertical swipe --
+ // which is most real thumb scrolling. An edge-zone-gated version of this was tried
+ // first, but on gesture-nav Android the screen edges belong to the OS's own back
+ // gesture, which wins before the app ever sees the touch (confirmed live: an edge
+ // swipe triggered system back, not the pager). So instead: disable the Pager's own
+ // gesture and drive it from a detector that works anywhere on screen but only commits
+ // to a page turn once the drag is clearly horizontal-dominant (>2x the vertical
+ // movement) -- ordinary scrolling, even sloppy/diagonal, stays vertical-dominant and
+ // is left completely unconsumed for the WebView to handle normally.
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .pointerInput(safeIds.size) {
+ var totalDx = 0f
+ var totalDy = 0f
+ var committedHorizontal = false
+ detectDragGestures(
+ onDragStart = {
+ totalDx = 0f
+ totalDy = 0f
+ committedHorizontal = false
+ },
+ onDragCancel = { committedHorizontal = false },
+ onDragEnd = {
+ if (committedHorizontal) {
+ val direction = when {
+ totalDx <= -swipeThresholdPx -> 1
+ totalDx >= swipeThresholdPx -> -1
+ else -> 0
+ }
+ if (direction != 0) {
+ val target = (pagerState.currentPage + direction).coerceIn(0, safeIds.size - 1)
+ scope.launch { pagerState.animateScrollToPage(target) }
+ }
+ }
+ committedHorizontal = false
+ },
+ onDrag = { change, dragAmount ->
+ totalDx += dragAmount.x
+ totalDy += dragAmount.y
+ if (!committedHorizontal &&
+ abs(totalDx) > directionLockPx &&
+ abs(totalDx) > abs(totalDy) * 2f
+ ) {
+ committedHorizontal = true
+ }
+ if (committedHorizontal) change.consume()
+ }
+ )
+ }
+ ) {
+ HorizontalPager(state = pagerState, userScrollEnabled = false, modifier = Modifier.fillMaxSize()) { page ->
+ ReaderContent(
+ articleId = safeIds[page],
+ onBack = onBack,
+ onOpenLink = onOpenLink,
+ onOpenBrowser = onOpenBrowser
+ )
+ }
}
}