Show help banner when Cloudflare captcha page is shown in WebView
This commit is contained in:
parent
cdc1c5efa3
commit
6d69caf59e
4 changed files with 92 additions and 56 deletions
|
@ -4,8 +4,9 @@ import android.content.pm.ApplicationInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.google.accompanist.web.AccompanistWebViewClient
|
import com.google.accompanist.web.AccompanistWebViewClient
|
||||||
import com.google.accompanist.web.LoadingState
|
import com.google.accompanist.web.LoadingState
|
||||||
|
@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator
|
||||||
import com.google.accompanist.web.rememberWebViewState
|
import com.google.accompanist.web.rememberWebViewState
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.system.getHtml
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -46,7 +52,53 @@ fun WebViewScreenContent(
|
||||||
) {
|
) {
|
||||||
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
||||||
val navigator = rememberWebViewNavigator()
|
val navigator = rememberWebViewNavigator()
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var currentUrl by remember { mutableStateOf(url) }
|
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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
|
@ -116,61 +168,38 @@ fun WebViewScreenContent(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
val webClient = remember {
|
Column(
|
||||||
object : AccompanistWebViewClient() {
|
modifier = Modifier.padding(contentPadding),
|
||||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
) {
|
||||||
super.onPageStarted(view, url, favicon)
|
if (showCloudflareHelp) {
|
||||||
url?.let {
|
WarningBanner(
|
||||||
currentUrl = it
|
textRes = R.string.information_cloudflare_help,
|
||||||
onUrlChange(it)
|
modifier = Modifier.clickable {
|
||||||
}
|
uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
|
||||||
}
|
},
|
||||||
|
)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class CloudflareInterceptor(
|
||||||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||||
// we don't crash the entire app
|
// we don't crash the entire app
|
||||||
catch (e: CloudflareBypassException) {
|
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) {
|
} catch (e: Exception) {
|
||||||
throw IOException(e)
|
throw IOException(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import android.content.pm.PackageManager
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
object WebViewUtil {
|
object WebViewUtil {
|
||||||
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
|
const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
|
||||||
|
@ -32,6 +34,10 @@ fun WebView.isOutdated(): Boolean {
|
||||||
return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
|
return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun WebView.getHtml(): String = suspendCancellableCoroutine {
|
||||||
|
evaluateJavascript("document.documentElement.outerHTML") { html -> it.resume(html) }
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
fun WebView.setDefaultSettings() {
|
fun WebView.setDefaultSettings() {
|
||||||
with(settings) {
|
with(settings) {
|
||||||
|
|
|
@ -923,6 +923,7 @@
|
||||||
<string name="information_empty_category">You have no categories. Tap the plus button to create one for organizing your library.</string>
|
<string name="information_empty_category">You have no categories. Tap the plus button to create one for organizing your library.</string>
|
||||||
<string name="information_empty_category_dialog">You don\'t have any categories yet.</string>
|
<string name="information_empty_category_dialog">You don\'t have any categories yet.</string>
|
||||||
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
|
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
|
||||||
|
<string name="information_cloudflare_help">Tap here for help with Cloudflare</string>
|
||||||
<string name="information_required_plain">*required</string>
|
<string name="information_required_plain">*required</string>
|
||||||
<!-- Do not translate "WebView" -->
|
<!-- Do not translate "WebView" -->
|
||||||
<string name="information_webview_required">WebView is required for Tachiyomi</string>
|
<string name="information_webview_required">WebView is required for Tachiyomi</string>
|
||||||
|
|
Reference in a new issue