diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-23 00:45:39 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-23 00:45:39 +0000 |
| commit | 5f3850ab73903010269df6009204a96f9d3e68ac (patch) | |
| tree | dd57986c6385d0f3b27bc697dba16aeee6c8fdee /app/src/main/java | |
| parent | bc891a67802c837d77726f618e68c2ac84ea22d7 (diff) | |
Preserve scroll position when the article list changes underneath you
A background fetch (the 5am alarm or the refresh button) inserting new
articles above the current viewport left LazyColumn pointing at the same
numeric index, which after the insert resolves to a different (newer)
article -- the visible list silently jumped forward instead of staying
where you were reading. Re-anchors to the same article by id at whatever
index it now sits at, restoring the exact scroll offset too.
Diffstat (limited to 'app/src/main/java')
| -rw-r--r-- | app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt | 26 |
1 files changed, 25 insertions, 1 deletions
diff --git a/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt b/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt index 7a99050..d87620d 100644 --- a/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt +++ b/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack @@ -98,6 +99,29 @@ fun ArticleListScreen( } }.collectAsState(initial = emptyList()) + // Hoisted (not LazyColumn's own default state) so the effect below can read/adjust it. + val listState = rememberLazyListState() + var previousArticleIds by remember(feedId) { mutableStateOf<List<String>>(emptyList()) } + + // A background fetch (the 5am alarm, or the explicit refresh button) can insert new + // articles above whatever's currently on screen. LazyColumn doesn't move + // firstVisibleItemIndex just because the backing list changed -- that index is only + // ever moved by an actual scroll or an explicit scrollToItem -- so after an insert, the + // SAME index now points at a DIFFERENT (newer) article, and the visible content jumps + // out from under you instead of staying where you were reading. Re-anchor to the same + // article, by id, at whatever index it now sits at, preserving the on-screen offset too + // so this doesn't even show as a snap -- just an uninterrupted read. + LaunchedEffect(visibleArticles) { + val anchorId = previousArticleIds.getOrNull(listState.firstVisibleItemIndex) + if (anchorId != null) { + val newIndex = visibleArticles.indexOfFirst { it.first.id == anchorId } + if (newIndex >= 0 && newIndex != listState.firstVisibleItemIndex) { + listState.scrollToItem(newIndex, listState.firstVisibleItemScrollOffset) + } + } + previousArticleIds = visibleArticles.map { it.first.id } + } + // Unread/Older/Saved mix articles from every feed, so each row names its source; a // single feed's own list already says so in the top bar, so rows there don't repeat it. val showFeedName = feedId == UNREAD_FEED_ID || feedId == OLDER_FEED_ID || feedId == SAVED_FEED_ID @@ -243,7 +267,7 @@ fun ArticleListScreen( }, snackbarHost = { SnackbarHost(snackbarHostState) } ) { padding -> - LazyColumn(modifier = Modifier.fillMaxSize().padding(padding)) { + LazyColumn(modifier = Modifier.fillMaxSize().padding(padding), state = listState) { items(visibleArticles, key = { it.first.id }) { (article, result) -> ArticleRow( article = article, |
