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