From bedf8a47a8b0601f4e3c253e4d2a7325c6ccbfc6 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 22 Jul 2026 18:56:47 +0000 Subject: 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. --- .../java/org/terst/feedreader/ui/ReaderScreen.kt | 77 ++++++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) (limited to 'app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt') 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 + ) + } } } -- cgit v1.2.3