1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
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<String?>(null) }
var webViewRef by remember(startUrl) { mutableStateOf<WebView?>(null) }
// Deliberately NOT "goBack() if the WebView has history, else exit" -- that means
// however many pages deep a search/login session went (search results -> article ->
// another link -> ...), leaving requires tapping back that many times first. Reported
// live as not making sense for this kind of multi-page browsing. Back always just
// closes the screen now, both the system gesture and the in-app arrow; there's no
// separate in-page-back affordance, matching how every other screen in this app behaves.
BackHandler(onBack = onBack)
Scaffold(
topBar = {
TopAppBar(
title = { Text(title ?: "Browser", maxLines = 1, overflow = TextOverflow.Ellipsis) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Close browser")
}
},
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)
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true)
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()
}
)
}
}
}
|