Catches pages like what Shinigami is currently showing. Also adjusts the banner to make it look more like part of the top AppBar so it looks less like part of the webpage. (cherry picked from commit c386d375dee2a846dd59395e6eda3a07311ed3d5)
217 lines
9.0 KiB
Kotlin
217 lines
9.0 KiB
Kotlin
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 {
|
|
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,
|
|
)
|
|
}
|
|
}
|