package org.terst.feedreader.ui import android.content.Intent import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.OpenInBrowser 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.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.unit.dp import androidx.compose.ui.viewinterop.AndroidView import org.terst.feedreader.readability.ReaderExtractor /** * A lightweight, non-persisted reader view for links tapped inside an article's * WebView -- extracts the linked page with the same Readability pipeline as the * main reader, but doesn't touch the DB (it's not a subscribed article), and * recursively re-opens itself for any further links tapped inside, so link-hopping * stays inside the app's reader instead of dropping into a raw WebView or the * system browser. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun LinkReaderScreen(url: String, onBack: () -> Unit, onOpenLink: (String) -> Unit) { val context = LocalContext.current var title by remember(url) { mutableStateOf(null) } var content by remember(url) { mutableStateOf(null) } var failed by remember(url) { mutableStateOf(false) } LaunchedEffect(url) { val extracted = ReaderExtractor.extract(context, url) val extractedContent = extracted?.content if (extractedContent != null) { title = extracted.title content = extractedContent } else { failed = true } } Scaffold( topBar = { TopAppBar( title = { Text(title ?: Uri.parse(url).host ?: "Article", maxLines = 1, overflow = TextOverflow.Ellipsis) }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.Default.ArrowBack, contentDescription = "Back") } }, actions = { IconButton(onClick = { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) }) { Icon(Icons.Default.OpenInBrowser, contentDescription = "Open in browser") } } ) } ) { padding -> Box(modifier = Modifier.fillMaxSize().padding(padding)) { val currentContent = content when { currentContent != null -> { val loadedContent = remember(url) { mutableStateOf(null) } AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> newReaderWebView(ctx, onOpenLink).apply { loadDataWithBaseURL(url, wrapReaderHtml(currentContent), "text/html", "UTF-8", null) loadedContent.value = currentContent } }, update = { webView -> if (loadedContent.value != currentContent) { webView.loadDataWithBaseURL(url, wrapReaderHtml(currentContent), "text/html", "UTF-8", null) loadedContent.value = currentContent } }, onRelease = { webView -> webView.destroy() } ) } failed -> Text( "Couldn't extract a reader view for this page -- try \"Open in browser\" instead.", modifier = Modifier.align(Alignment.Center).padding(24.dp), color = MaterialTheme.colorScheme.onSurfaceVariant ) else -> CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } }