diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt
index 18c1d9517b..e3da2dd6f4 100644
--- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt
+++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt
@@ -4,8 +4,9 @@ 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.fillMaxSize
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@@ -17,9 +18,11 @@ 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.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.LoadingState
@@ -28,9 +31,12 @@ 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
@@ -46,7 +52,53 @@ fun WebViewScreenContent(
) {
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 = "Checking if the site connection is secure" 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 = {
@@ -116,61 +168,38 @@ fun WebViewScreenContent(
}
},
) { contentPadding ->
- 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 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)
- }
+ Column(
+ modifier = Modifier.padding(contentPadding),
+ ) {
+ if (showCloudflareHelp) {
+ WarningBanner(
+ textRes = R.string.information_cloudflare_help,
+ modifier = Modifier.clickable {
+ uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
+ },
+ )
}
+
+ WebView(
+ state = state,
+ modifier = Modifier.weight(1f),
+ 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,
+ )
}
-
- WebView(
- state = state,
- modifier = Modifier
- .padding(contentPadding)
- .fillMaxSize(),
- 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,
- )
}
}
diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt
index 3814319011..25b5a140a6 100644
--- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt
+++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt
@@ -44,7 +44,7 @@ class CloudflareInterceptor(
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// we don't crash the entire app
catch (e: CloudflareBypassException) {
- throw IOException(context.getString(R.string.information_cloudflare_bypass_failure))
+ throw IOException(context.getString(R.string.information_cloudflare_bypass_failure), e)
} catch (e: Exception) {
throw IOException(e)
}
diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
index 7da98f8100..4fcaaceed2 100644
--- a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
+++ b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
@@ -6,8 +6,10 @@ import android.content.pm.PackageManager
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
+import kotlinx.coroutines.suspendCancellableCoroutine
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
+import kotlin.coroutines.resume
object WebViewUtil {
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
@@ -32,6 +34,10 @@ fun WebView.isOutdated(): Boolean {
return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
}
+suspend fun WebView.getHtml(): String = suspendCancellableCoroutine {
+ evaluateJavascript("document.documentElement.outerHTML") { html -> it.resume(html) }
+}
+
@SuppressLint("SetJavaScriptEnabled")
fun WebView.setDefaultSettings() {
with(settings) {
diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index 96b714ba93..2829d51a24 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -923,6 +923,7 @@
You have no categories. Tap the plus button to create one for organizing your library.
You don\'t have any categories yet.
Failed to bypass Cloudflare
+ Tap here for help with Cloudflare
*required
WebView is required for Tachiyomi