summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/ui/BrowserScreen.kt
blob: 6eb5b71d83678bb216def35fd1b23ab39e58c76c (plain)
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()
                }
            )
        }
    }
}