123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- package eu.kanade.presentation.webview
- import android.content.pm.ApplicationInfo
- import android.graphics.Bitmap
- import android.webkit.WebResourceRequest
- import android.webkit.WebView
- import androidx.compose.foundation.clickable
- import androidx.compose.foundation.layout.Box
- import androidx.compose.foundation.layout.Column
- import androidx.compose.foundation.layout.fillMaxSize
- import androidx.compose.foundation.layout.fillMaxWidth
- import androidx.compose.foundation.layout.padding
- import androidx.compose.material.icons.Icons
- import androidx.compose.material.icons.outlined.ArrowBack
- import androidx.compose.material.icons.outlined.ArrowForward
- import androidx.compose.material.icons.outlined.Close
- import androidx.compose.material3.LinearProgressIndicator
- import androidx.compose.material3.MaterialTheme
- import androidx.compose.material3.Surface
- import androidx.compose.runtime.Composable
- import androidx.compose.runtime.getValue
- import androidx.compose.runtime.mutableStateOf
- import androidx.compose.runtime.remember
- import androidx.compose.runtime.rememberCoroutineScope
- import androidx.compose.runtime.setValue
- import androidx.compose.ui.Alignment
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.draw.clip
- import androidx.compose.ui.platform.LocalUriHandler
- import androidx.compose.ui.res.stringResource
- import androidx.compose.ui.unit.dp
- import com.google.accompanist.web.AccompanistWebViewClient
- import com.google.accompanist.web.LoadingState
- import com.google.accompanist.web.WebView
- import com.google.accompanist.web.rememberWebViewNavigator
- import com.google.accompanist.web.rememberWebViewState
- import eu.kanade.presentation.components.AppBar
- import eu.kanade.presentation.components.AppBarActions
- import eu.kanade.presentation.components.WarningBanner
- import eu.kanade.tachiyomi.BuildConfig
- import eu.kanade.tachiyomi.R
- import eu.kanade.tachiyomi.util.system.getHtml
- import eu.kanade.tachiyomi.util.system.setDefaultSettings
- import kotlinx.coroutines.launch
- import tachiyomi.presentation.core.components.material.Scaffold
- @Composable
- fun WebViewScreenContent(
- onNavigateUp: () -> Unit,
- initialTitle: String?,
- url: String,
- headers: Map<String, String> = emptyMap(),
- onUrlChange: (String) -> Unit = {},
- onShare: (String) -> Unit,
- onOpenInBrowser: (String) -> Unit,
- onClearCookies: (String) -> Unit,
- ) {
- val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
- val navigator = rememberWebViewNavigator()
- val uriHandler = LocalUriHandler.current
- val scope = rememberCoroutineScope()
- var currentUrl by remember { mutableStateOf(url) }
- var showCloudflareHelp by remember { mutableStateOf(false) }
- val webClient = remember {
- object : AccompanistWebViewClient() {
- override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
- super.onPageStarted(view, url, favicon)
- url?.let {
- currentUrl = it
- onUrlChange(it)
- }
- }
- override fun onPageFinished(view: WebView, url: String?) {
- super.onPageFinished(view, url)
- scope.launch {
- val html = view.getHtml()
- showCloudflareHelp = "window._cf_chl_opt" in html || "Ray ID is" in html
- }
- }
- override fun doUpdateVisitedHistory(
- view: WebView,
- url: String?,
- isReload: Boolean,
- ) {
- super.doUpdateVisitedHistory(view, url, isReload)
- url?.let {
- currentUrl = it
- onUrlChange(it)
- }
- }
- override fun shouldOverrideUrlLoading(
- view: WebView?,
- request: WebResourceRequest?,
- ): Boolean {
- request?.let {
- // Don't attempt to open blobs as webpages
- if (it.url.toString().startsWith("blob:http")) {
- return false
- }
- // Continue with request, but with custom headers
- view?.loadUrl(it.url.toString(), headers)
- }
- return super.shouldOverrideUrlLoading(view, request)
- }
- }
- }
- Scaffold(
- topBar = {
- Box {
- Column {
- AppBar(
- title = state.pageTitle ?: initialTitle,
- subtitle = currentUrl,
- navigateUp = onNavigateUp,
- navigationIcon = Icons.Outlined.Close,
- actions = {
- AppBarActions(
- listOf(
- AppBar.Action(
- title = stringResource(R.string.action_webview_back),
- icon = Icons.Outlined.ArrowBack,
- onClick = {
- if (navigator.canGoBack) {
- navigator.navigateBack()
- }
- },
- enabled = navigator.canGoBack,
- ),
- AppBar.Action(
- title = stringResource(R.string.action_webview_forward),
- icon = Icons.Outlined.ArrowForward,
- onClick = {
- if (navigator.canGoForward) {
- navigator.navigateForward()
- }
- },
- enabled = navigator.canGoForward,
- ),
- AppBar.OverflowAction(
- title = stringResource(R.string.action_webview_refresh),
- onClick = { navigator.reload() },
- ),
- AppBar.OverflowAction(
- title = stringResource(R.string.action_share),
- onClick = { onShare(currentUrl) },
- ),
- AppBar.OverflowAction(
- title = stringResource(R.string.action_open_in_browser),
- onClick = { onOpenInBrowser(currentUrl) },
- ),
- AppBar.OverflowAction(
- title = stringResource(R.string.pref_clear_cookies),
- onClick = { onClearCookies(currentUrl) },
- ),
- ),
- )
- },
- )
- if (showCloudflareHelp) {
- Surface(
- modifier = Modifier.padding(8.dp),
- ) {
- WarningBanner(
- textRes = R.string.information_cloudflare_help,
- modifier = Modifier
- .clip(MaterialTheme.shapes.small)
- .clickable {
- uriHandler.openUri(
- "https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare",
- )
- },
- )
- }
- }
- }
- when (val loadingState = state.loadingState) {
- is LoadingState.Initializing -> LinearProgressIndicator(
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.BottomCenter),
- )
- is LoadingState.Loading -> LinearProgressIndicator(
- progress = (loadingState as? LoadingState.Loading)?.progress ?: 1f,
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.BottomCenter),
- )
- else -> {}
- }
- }
- },
- ) { contentPadding ->
- WebView(
- state = state,
- modifier = Modifier
- .fillMaxSize()
- .padding(contentPadding),
- navigator = navigator,
- onCreated = { webView ->
- webView.setDefaultSettings()
- // Debug mode (chrome://inspect/#devices)
- if (BuildConfig.DEBUG &&
- 0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
- ) {
- WebView.setWebContentsDebuggingEnabled(true)
- }
- headers["user-agent"]?.let {
- webView.settings.userAgentString = it
- }
- },
- client = webClient,
- )
- }
- }
|