summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/ui
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/ui')
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt21
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt15
2 files changed, 26 insertions, 10 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 9d88374..a3edebe 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ArticleListScreen.kt
@@ -43,8 +43,10 @@ import androidx.compose.ui.unit.dp
import androidx.work.WorkInfo
import androidx.work.WorkManager
import coil.compose.AsyncImage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import org.terst.feedreader.data.AppDatabase
import org.terst.feedreader.data.ArticleListItem
@@ -58,7 +60,7 @@ import java.util.Date
private const val TAG = "ArticleListScreen"
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, ExperimentalCoroutinesApi::class)
@Composable
fun ArticleListScreen(
feedId: String,
@@ -70,11 +72,16 @@ fun ArticleListScreen(
val db = remember { AppDatabase.get(context) }
val scope = rememberCoroutineScope()
val cutoff = remember(feedId) { System.currentTimeMillis() - RECENT_WINDOW_MS }
+ val settings = remember { Settings(context) }
+ // Unread/Older re-derive their query every time lastFetchAt changes (flatMapLatest),
+ // not just once at screen entry -- a fetch completing while this screen is open should
+ // immediately drop whatever was read-but-lingering, same as if you'd navigated back in.
+ // See observeUnread's comment for why sinceFetch is here at all.
val articleFlow = remember(feedId) {
when (feedId) {
- UNREAD_FEED_ID -> db.articleDao().observeUnread(cutoff)
- OLDER_FEED_ID -> db.articleDao().observeOlderUnread(cutoff)
+ UNREAD_FEED_ID -> settings.lastFetchAt.flatMapLatest { since -> db.articleDao().observeUnread(cutoff, since) }
+ OLDER_FEED_ID -> settings.lastFetchAt.flatMapLatest { since -> db.articleDao().observeOlderUnread(cutoff, since) }
SAVED_FEED_ID -> db.articleDao().observeSaved()
else -> db.articleDao().observeByFeed(feedId)
}
@@ -99,7 +106,6 @@ fun ArticleListScreen(
val showFetchStatus = feedId == UNREAD_FEED_ID || feedId == OLDER_FEED_ID
val snackbarHostState = remember { SnackbarHostState() }
- val settings = remember { Settings(context) }
val fetchWorkInfos by remember {
WorkManager.getInstance(context).getWorkInfosByTagFlow(FeedFetchWorker.FETCH_WORK_TAG)
// A pure status readout, not load-bearing for the article list itself -- an
@@ -223,6 +229,11 @@ fun ArticleListScreen(
}
}
+// Shared with ReaderWebView's wrapReaderHtml (the reader's own date header) so both
+// places format a publishedAt timestamp identically.
+fun absoluteDateText(timestamp: Long): String =
+ DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(timestamp))
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ArticleRow(
@@ -233,7 +244,7 @@ private fun ArticleRow(
onClick: () -> Unit,
onMarkReadFrom: () -> Unit
) {
- val dateText = article.publishedAt?.let { DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(it)) }
+ val dateText = article.publishedAt?.let { absoluteDateText(it) }
val metaText = listOfNotNull(if (showFeedName) article.feedTitle else null, dateText)
.joinToString(" · ")
.ifEmpty { null }
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 4317073..4c74e5a 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
@@ -57,6 +57,7 @@ import kotlinx.coroutines.launch
import org.terst.feedreader.data.AppDatabase
import org.terst.feedreader.data.ArticleEntity
import org.terst.feedreader.data.FeedEntity
+import org.terst.feedreader.data.Settings
import org.terst.feedreader.data.ensureStandaloneArticle
import org.terst.feedreader.data.saveArticleToStash
import org.terst.feedreader.data.setReaderModeEnabled
@@ -96,6 +97,7 @@ fun ReaderScreen(
) {
val context = LocalContext.current
val db = remember { AppDatabase.get(context) }
+ val settings = remember { Settings(context) }
val scope = rememberCoroutineScope()
var previousId by remember(articleId) { mutableStateOf<String?>(null) }
@@ -104,11 +106,14 @@ fun ReaderScreen(
// Looked up fresh every time articleId changes (i.e. every navigation), not cached --
// always reflects the CURRENT live Unread/Older/Saved/feed ordering, so there's no
// stale-snapshot state to fall out of sync with anything, on rotation or otherwise.
+ // sinceFetch (Settings.lastFetchAt) is what keeps a just-read article from vanishing
+ // out of this same list before Previous/Next can find it -- see observeUnread's comment.
LaunchedEffect(articleId) {
val cutoff = System.currentTimeMillis() - RECENT_WINDOW_MS
+ val sinceFetch = settings.lastFetchAt.first()
val flow = when (feedId) {
- UNREAD_FEED_ID -> db.articleDao().observeUnread(cutoff)
- OLDER_FEED_ID -> db.articleDao().observeOlderUnread(cutoff)
+ UNREAD_FEED_ID -> db.articleDao().observeUnread(cutoff, sinceFetch)
+ OLDER_FEED_ID -> db.articleDao().observeOlderUnread(cutoff, sinceFetch)
SAVED_FEED_ID -> db.articleDao().observeSaved()
else -> db.articleDao().observeByFeed(feedId)
}
@@ -124,14 +129,14 @@ fun ReaderScreen(
fun goToPreviousArticle() {
val target = previousId ?: return
scope.launch {
- db.articleDao().setRead(articleId, true)
+ db.articleDao().setRead(articleId, true, System.currentTimeMillis())
onNavigateToArticle(target)
}
}
fun goToNextArticle() {
val target = nextId ?: return
scope.launch {
- db.articleDao().setRead(articleId, true)
+ db.articleDao().setRead(articleId, true, System.currentTimeMillis())
onNavigateToArticle(target)
}
}
@@ -345,7 +350,7 @@ private fun ReaderContent(
val a = currentArticle ?: return@DropdownMenuItem
article = a.copy(isRead = false)
scope.launch {
- db.articleDao().setRead(a.id, false)
+ db.articleDao().setRead(a.id, false, null)
snackbarHostState.showSnackbar("Marked unread")
}
}