diff options
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.kt | 58 |
1 files changed, 50 insertions, 8 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 cef17c5..2177d72 100644 --- a/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt +++ b/app/src/main/java/org/terst/feedreader/ui/ReaderScreen.kt @@ -5,6 +5,7 @@ import android.net.Uri import android.webkit.WebView import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager @@ -20,6 +21,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -35,11 +37,13 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.viewinterop.AndroidView import kotlinx.coroutines.flow.first 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.Net import org.terst.feedreader.data.Settings import org.terst.feedreader.readability.ReaderExtractor @@ -100,6 +104,7 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { val snackbarHostState = remember { SnackbarHostState() } var article by remember(articleId) { mutableStateOf<ArticleEntity?>(null) } + var feed by remember(articleId) { mutableStateOf<FeedEntity?>(null) } var feedContent by remember(articleId) { mutableStateOf<String?>(null) } var readerContent by remember(articleId) { mutableStateOf<String?>(null) } var isReaderMode by remember(articleId) { mutableStateOf(false) } @@ -110,12 +115,14 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { val loaded = db.articleDao().getById(articleId) ?: return@LaunchedEffect article = loaded db.articleDao().setRead(articleId, true) + val loadedFeed = loaded.feedId?.let { db.feedDao().getById(it) } + feed = loadedFeed feedContent = loaded.feedContentHtml readerContent = loaded.extractedContent val plainLength = feedContent?.replace(Regex("<[^>]*>"), "")?.trim()?.length ?: 0 - if (readerContent == null && plainLength < THIN_CONTENT_THRESHOLD) { + 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 @@ -131,6 +138,7 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { fun toggleReaderMode() { val a = article ?: return + if (feed?.readerModeDisabled == true) return if (isReaderMode) { isReaderMode = false return @@ -157,7 +165,20 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { Scaffold( topBar = { TopAppBar( - title = { Text(article?.title.orEmpty(), maxLines = 1) }, + title = { + Column { + Text(article?.title.orEmpty(), maxLines = 1, overflow = TextOverflow.Ellipsis) + feed?.title?.let { + Text( + it, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.Default.ArrowBack, contentDescription = "Back") @@ -166,9 +187,9 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { 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 (thin feed content), - // reader mode IS the only content, so there's nothing to toggle between. - if (feedContent != null) { + // 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() }) { Icon( if (isReaderMode) Icons.Filled.Article else Icons.Outlined.Article, @@ -223,15 +244,28 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { val content = if (isReaderMode) readerContent else (feedContent ?: readerContent) val baseUrl = article?.link if (content != null) { + // AndroidView's update lambda reruns on every recomposition of this + // composable -- including ones unrelated to content, like isSaving + // flipping while a Stash save is in flight. Reloading the WebView's HTML + // on those unrelated recompositions was resetting scroll position mid-swipe, + // which is what made scrolling feel unsmooth/janky. Track what's actually + // loaded and only reload when the content string itself changes. + val loadedContent = remember(articleId) { mutableStateOf<String?>(null) } AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> WebView(ctx).apply { settings.javaScriptEnabled = false loadDataWithBaseURL(baseUrl, wrapHtml(content), "text/html", "UTF-8", null) + loadedContent.value = content + } + }, + update = { webView -> + if (loadedContent.value != content) { + webView.loadDataWithBaseURL(baseUrl, wrapHtml(content), "text/html", "UTF-8", null) + loadedContent.value = content } }, - update = { webView -> webView.loadDataWithBaseURL(baseUrl, wrapHtml(content), "text/html", "UTF-8", null) }, // AndroidView does not destroy() the underlying WebView on its own -- // without this, swiping through many articles (each page holding its own // WebView) leaks a WebView's native resources per page and eventually @@ -245,9 +279,17 @@ private fun ReaderContent(articleId: String, onBack: () -> Unit) { } } +// Hardcoded dark palette, not @media (prefers-color-scheme) -- the app itself always +// forces dark colorScheme() regardless of system setting (see MainActivity), so the +// reader content should match rather than following a system light/dark that the rest +// of the app ignores. private fun wrapHtml(bodyContent: String): String = """ <html><head><meta name="viewport" content="width=device-width, initial-scale=1"> - <style>body { font-family: sans-serif; font-size: 18px; line-height: 1.5; padding: 16px; } - img { max-width: 100%; height: auto; }</style></head> + <style> + body { font-family: sans-serif; font-size: 18px; line-height: 1.5; padding: 16px; + background-color: #121212; color: #e1e1e1; } + a { color: #8ab4f8; } + img { max-width: 100%; height: auto; } + </style></head> <body>$bodyContent</body></html> """.trimIndent() |
