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/FeedListScreen.kt23
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt87
2 files changed, 64 insertions, 46 deletions
diff --git a/app/src/main/java/org/terst/feedreader/ui/FeedListScreen.kt b/app/src/main/java/org/terst/feedreader/ui/FeedListScreen.kt
index f8ea504..47f51fc 100644
--- a/app/src/main/java/org/terst/feedreader/ui/FeedListScreen.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/FeedListScreen.kt
@@ -27,6 +27,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
@@ -73,6 +75,7 @@ fun FeedListScreen(
val olderUnreadCount by remember { db.articleDao().observeOlderUnreadCount(cutoff) }.collectAsState(initial = 0)
var menuExpanded by remember { mutableStateOf(false) }
var pendingDelete by remember { mutableStateOf<FeedEntity?>(null) }
+ val snackbarHostState = remember { SnackbarHostState() }
pendingDelete?.let { feed ->
AlertDialog(
@@ -121,7 +124,8 @@ fun FeedListScreen(
FloatingActionButton(onClick = onAddFeed) {
Icon(Icons.Default.Add, contentDescription = "Add feed")
}
- }
+ },
+ snackbarHost = { SnackbarHost(snackbarHostState) }
) { padding ->
LazyColumn(modifier = Modifier.fillMaxSize().padding(padding)) {
item {
@@ -152,7 +156,14 @@ fun FeedListScreen(
onClick = { onOpenFeed(feed.id) },
onDelete = { pendingDelete = feed },
onToggleReaderMode = {
- scope.launch { db.feedDao().update(feed.copy(readerModeDisabled = !feed.readerModeDisabled)) }
+ val newEnabled = !feed.readerModeEnabled
+ scope.launch {
+ db.feedDao().update(feed.copy(readerModeEnabled = newEnabled))
+ val label = feed.title.ifBlank { "this feed" }
+ snackbarHostState.showSnackbar(
+ if (newEnabled) "Reader mode enabled for $label" else "Reader mode disabled for $label"
+ )
+ }
}
)
HorizontalDivider()
@@ -184,11 +195,11 @@ private fun FeedRow(
}
IconButton(onClick = onToggleReaderMode) {
Icon(
- if (feed.readerModeDisabled) Icons.Outlined.Article else Icons.Filled.Article,
- contentDescription = if (feed.readerModeDisabled)
- "Reader mode disabled for this feed -- tap to re-enable"
- else
+ if (feed.readerModeEnabled) Icons.Filled.Article else Icons.Outlined.Article,
+ contentDescription = if (feed.readerModeEnabled)
"Reader mode enabled for this feed -- tap to disable"
+ else
+ "Reader mode disabled for this feed -- tap to enable"
)
}
IconButton(onClick = onDelete) {
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 4becea6..806926c 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt
@@ -59,10 +59,6 @@ import org.terst.feedreader.stash.StashClient
import org.terst.feedreader.stash.StashSaveResult
import java.net.URLEncoder
-// 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.
-private const val THIN_CONTENT_THRESHOLD = 500
-
/**
* Swipeable wrapper: snapshots the article-id order of the list this reader
* was opened from (Unread/Saved/a specific feed) ONCE at entry, then pages
@@ -191,44 +187,53 @@ private fun ReaderContent(
feedContent = loaded.feedContentHtml
readerContent = loaded.extractedContent
- val plainLength = feedContent?.replace(Regex("<[^>]*>"), "")?.trim()?.length ?: 0
- if (loadedFeed?.readerModeDisabled != true && readerContent == null && plainLength < THIN_CONTENT_THRESHOLD) {
- // Feed content is too thin to be the real article -- auto-extract, since in this
- // case reader mode isn't really an alternative view, it's the only real content.
- isExtracting = true
- val extracted = ReaderExtractor.extract(context, loaded.link)
- isExtracting = false
- extracted?.content?.let {
- readerContent = it
- db.articleDao().setExtractedContent(articleId, it)
- }
+ // Reader mode is NEVER heuristic/automatic (no more "content looks thin, switch
+ // for you") -- it strictly follows the feed's persisted, explicitly-set opt-in.
+ // Once a feed has it on, articles from it default to the extracted view and
+ // auto-extract on open (that's the point of persisting it per-feed: no re-tapping
+ // per article), but nothing here ever turns it on by itself.
+ if (loadedFeed?.readerModeEnabled == true) {
isReaderMode = true
+ if (readerContent == null) {
+ isExtracting = true
+ val extracted = ReaderExtractor.extract(context, loaded.link)
+ isExtracting = false
+ extracted?.content?.let {
+ readerContent = it
+ db.articleDao().setExtractedContent(articleId, it)
+ }
+ }
}
}
- fun toggleReaderMode() {
+ // The toggle now sets reader mode for the ARTICLE'S FEED, persisted immediately, with
+ // a confirmation snackbar -- not a per-article, this-session-only view flip. Tapping it
+ // is the one and only way reader mode ever turns on.
+ fun toggleFeedReaderMode() {
+ val f = feed ?: return
val a = article ?: return
- if (feed?.readerModeDisabled == true) return
- if (isReaderMode) {
- isReaderMode = false
- return
- }
- if (readerContent != null) {
- isReaderMode = true
- return
- }
+ val newEnabled = !f.readerModeEnabled
+ feed = f.copy(readerModeEnabled = newEnabled)
scope.launch {
- isExtracting = true
- val extracted = ReaderExtractor.extract(context, a.link)
- isExtracting = false
- val content = extracted?.content
- if (content != null) {
- readerContent = content
- db.articleDao().setExtractedContent(articleId, content)
+ db.feedDao().update(f.copy(readerModeEnabled = newEnabled))
+ val feedLabel = f.title.ifBlank { "this feed" }
+ if (newEnabled) {
+ if (readerContent == null) {
+ isExtracting = true
+ val extracted = ReaderExtractor.extract(context, a.link)
+ isExtracting = false
+ val content = extracted?.content
+ if (content != null) {
+ readerContent = content
+ db.articleDao().setExtractedContent(articleId, content)
+ }
+ }
isReaderMode = true
+ snackbarHostState.showSnackbar("Reader mode enabled for $feedLabel")
} else {
- snackbarHostState.showSnackbar("Couldn't extract a reader view for this article")
+ isReaderMode = false
+ snackbarHostState.showSnackbar("Reader mode disabled for $feedLabel")
}
}
}
@@ -257,14 +262,16 @@ private fun ReaderContent(
},
actions = {
val currentArticle = article
- // Only offer the toggle when there's an actual feed-provided alternative to
- // switch back to (if we already had to auto-extract for thin feed content,
- // reader mode IS the only content) and the feed hasn't opted out of it.
- if (feedContent != null && feed?.readerModeDisabled != true) {
- IconButton(enabled = !isExtracting, onClick = { toggleReaderMode() }) {
+ // Sets reader mode for the whole feed (persisted), not just this article --
+ // shown whenever the article belongs to an actual subscribed feed.
+ if (feed != null) {
+ IconButton(enabled = !isExtracting, onClick = { toggleFeedReaderMode() }) {
Icon(
- if (isReaderMode) Icons.Filled.Article else Icons.Outlined.Article,
- contentDescription = "Toggle reader view"
+ if (feed?.readerModeEnabled == true) Icons.Filled.Article else Icons.Outlined.Article,
+ contentDescription = if (feed?.readerModeEnabled == true)
+ "Reader mode enabled for this feed -- tap to disable"
+ else
+ "Reader mode disabled for this feed -- tap to enable"
)
}
}