From fd49bacfb92ef8d29cbad56419acd016ff40f357 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sat, 22 Aug 2026 01:00:02 +0000 Subject: In-app YouTube playback, shared ConfirmDialog, hardened link handling - ReaderWebView/LinkReaderScreen/ReaderScreen: tapping a YouTube link inside an article now opens an in-app player (youtube-nocookie.com, JS scoped to just that dialog) instead of handing off to the YouTube app/browser. Falls back to external-open when a confident video ID can't be extracted (playlist/channel links). - A subframe (ad/embed iframe) auto-navigating is now blocked outright instead of being treated like a real link tap -- it fired the exact same callback as a user tap, with no interaction required. - wrapReaderHtml can now show a fallback hero image + date when the feed's own body HTML has no image of its own. - ConfirmDialog: the generic "confirm before doing something irreversible" dialog shape was independently rebuilt from scratch at FeedListScreen's unsubscribe and ArticleListScreen's mark-read-from- here confirmations. Both now share one composable; the latter also chunks its batch update (markReadBatchChunked) so a long-unread feed can't exceed SQLite's per-statement bound-variable limit. --- .../org/terst/feedreader/ui/LinkReaderScreen.kt | 49 ++++++++++++++++------ 1 file changed, 37 insertions(+), 12 deletions(-) (limited to 'app/src/main/java/org/terst/feedreader/ui/LinkReaderScreen.kt') diff --git a/app/src/main/java/org/terst/feedreader/ui/LinkReaderScreen.kt b/app/src/main/java/org/terst/feedreader/ui/LinkReaderScreen.kt index 3451833..0f7540d 100644 --- a/app/src/main/java/org/terst/feedreader/ui/LinkReaderScreen.kt +++ b/app/src/main/java/org/terst/feedreader/ui/LinkReaderScreen.kt @@ -39,6 +39,7 @@ 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.unit.dp import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.viewinterop.AndroidView import kotlinx.coroutines.launch @@ -80,6 +81,7 @@ fun LinkReaderScreen(url: String, onBack: () -> Unit, onOpenLink: (String) -> Un var isReextracting by remember(url) { mutableStateOf(false) } var moreMenuExpanded by remember(url) { mutableStateOf(false) } var longPressedLink by remember(url) { mutableStateOf(null) } + var youtubeVideoId by remember(url) { mutableStateOf(null) } LaunchedEffect(url) { val existing = db.articleDao().getById(articleId) @@ -230,7 +232,12 @@ fun LinkReaderScreen(url: String, onBack: () -> Unit, onOpenLink: (String) -> Un AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> - newReaderWebView(ctx, onOpenLink, onLongPressLink = { longPressedLink = it }).apply { + newReaderWebView( + ctx, + onOpenLink, + onLongPressLink = { longPressedLink = it }, + onYouTubeVideo = { youtubeVideoId = it } + ).apply { loadDataWithBaseURL(url, wrapReaderHtml(currentExtractedContent, extractedTitle), "text/html", "UTF-8", null) loadedContent.value = currentExtractedContent } @@ -247,21 +254,39 @@ fun LinkReaderScreen(url: String, onBack: () -> Unit, onOpenLink: (String) -> Un // Reader mode off, or extraction failed -- fall back to the live page // itself (JS on) rather than leaving the screen blank. !isReaderMode || extractionFailed -> { - AndroidView( - modifier = Modifier.fillMaxSize(), - factory = { ctx -> - WebView(ctx).apply { - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - loadUrl(url) - } - }, - onRelease = { webView -> webView.destroy() } - ) + if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) { + // `url` isn't necessarily a link the user themselves tapped: it can + // also be whatever a WebView's shouldOverrideUrlLoading reported for + // ANY navigation attempt inside an article (see ReaderWebView's + // onOpenLink), including one triggered by the article's own HTML + // linking to a non-http scheme (intent://, tel:, etc.). Refusing to + // load anything but http(s) here means this JS-enabled WebView can't + // become a sink for whatever scheme a crafted article handed it. + Text( + "Can't open this link: $url", + modifier = Modifier.fillMaxSize().padding(16.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { ctx -> + WebView(ctx).apply { + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + loadUrl(url) + } + }, + onRelease = { webView -> webView.destroy() } + ) + } } else -> CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } + youtubeVideoId?.let { videoId -> + YouTubePlayerDialog(videoId = videoId, onDismiss = { youtubeVideoId = null }) + } longPressedLink?.let { linkUrl -> LinkActionsDialog( url = linkUrl, -- cgit v1.2.3