package org.terst.feedreader.ui import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.webkit.CookieManager import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.compose.BackHandler 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.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.viewinterop.AndroidView /** * A real, interactive mini-browser -- JS and DOM storage on, ordinary navigation, no * Readability interception. Unlike LinkReaderScreen (always tries to extract a clean * article view), this is for the two cases where the user needs to actually USE a * page rather than just read it: browsing search results to find an alternate, * non-paywalled source for a story, or logging into a site they already subscribe to. * * Login needs no extra plumbing beyond turning cookies on: Android's CookieManager is * a single process-wide store shared by every WebView in the app, so once a session * cookie is set here, the reader's (JS-off) extraction WebView picks it up automatically * on the next visit to that domain -- same as a normal browser remembering you're signed in. */ @SuppressLint("SetJavaScriptEnabled") @OptIn(ExperimentalMaterial3Api::class) @Composable fun BrowserScreen(startUrl: String, onBack: () -> Unit) { val context = LocalContext.current var title by remember(startUrl) { mutableStateOf(null) } var webViewRef by remember(startUrl) { mutableStateOf(null) } // Real browser-style back: step back through THIS WebView's own page history first // (search results -> article -> another link -> ...) and only close the screen once // there's nowhere left to go back to. An earlier version made back always close the // screen outright, on the theory that stepping through a deep session one page at a // time was tedious -- but that meant a single back press (system gesture or the in-app // arrow) could silently throw away an entire multi-page browsing/login session, which // is worse: there's no way to recover it, and it doesn't match how every other browser // (or in-app browser view) on the platform behaves. Both the system back gesture and // the in-app arrow go through the same path below, so they behave identically. fun handleBack() { val webView = webViewRef if (webView != null && webView.canGoBack()) { webView.goBack() } else { onBack() } } BackHandler(onBack = ::handleBack) Scaffold( topBar = { TopAppBar( title = { Text(title ?: "Browser", maxLines = 1, overflow = TextOverflow.Ellipsis) }, navigationIcon = { IconButton(onClick = ::handleBack) { Icon(Icons.Default.ArrowBack, contentDescription = "Back") } }, actions = { IconButton(onClick = { val url = webViewRef?.url ?: startUrl context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) }) { Icon(Icons.Default.OpenInBrowser, contentDescription = "Open in browser") } } ) } ) { padding -> Box(modifier = Modifier.fillMaxSize().padding(padding)) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> WebView(ctx).apply { settings.javaScriptEnabled = true settings.domStorageEnabled = true CookieManager.getInstance().setAcceptCookie(true) // Deliberately NOT setAcceptThirdPartyCookies: the login-persistence // feature this screen exists for (see the class doc comment) only // needs a site's own FIRST-party session cookie to reach the // extraction WebView -- third-party cookies are for cross-site // trackers embedded within whatever page is loaded here, which this // feature has no use for and which only adds tracking surface shared // with every other WebView in the app. webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView, url: String) { title = view.title } } webChromeClient = WebChromeClient() loadUrl(startUrl) webViewRef = this } }, onRelease = { webView -> // Flush explicitly so a just-completed login survives even if the // process is killed shortly after (modern WebView auto-persists, but // this makes it immediate rather than relying on that timing). CookieManager.getInstance().flush() webView.destroy() } ) } } }