pull/1352/head
parent
fbee6a9dd3
commit
c2ae61b71c
@ -0,0 +1,174 @@ |
|||||||
|
package io.legado.app.ui.web |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.pm.ActivityInfo |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Bundle |
||||||
|
import android.view.View |
||||||
|
import android.webkit.* |
||||||
|
import androidx.activity.viewModels |
||||||
|
import androidx.webkit.WebSettingsCompat |
||||||
|
import androidx.webkit.WebViewFeature |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.VMBaseActivity |
||||||
|
import io.legado.app.constant.AppConst |
||||||
|
import io.legado.app.databinding.ActivityWebViewBinding |
||||||
|
import io.legado.app.help.AppConfig |
||||||
|
import io.legado.app.lib.dialogs.SelectItem |
||||||
|
import io.legado.app.model.Download |
||||||
|
import io.legado.app.ui.association.OnLineImportActivity |
||||||
|
import io.legado.app.ui.document.HandleFileContract |
||||||
|
import io.legado.app.utils.* |
||||||
|
import io.legado.app.utils.viewbindingdelegate.viewBinding |
||||||
|
|
||||||
|
class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() { |
||||||
|
|
||||||
|
override val binding by viewBinding(ActivityWebViewBinding::inflate) |
||||||
|
override val viewModel by viewModels<WebViewModel>() |
||||||
|
private val imagePathKey = "imagePath" |
||||||
|
private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null |
||||||
|
private var webPic: String? = null |
||||||
|
private val saveImage = registerForActivityResult(HandleFileContract()) { |
||||||
|
it ?: return@registerForActivityResult |
||||||
|
ACache.get(this).put(imagePathKey, it.toString()) |
||||||
|
viewModel.saveImage(webPic, it.toString()) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||||
|
initWebView() |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressLint("JavascriptInterface") |
||||||
|
private fun initWebView() { |
||||||
|
binding.webView.webChromeClient = CustomWebChromeClient() |
||||||
|
binding.webView.webViewClient = CustomWebViewClient() |
||||||
|
binding.webView.settings.apply { |
||||||
|
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW |
||||||
|
domStorageEnabled = true |
||||||
|
allowContentAccess = true |
||||||
|
} |
||||||
|
binding.webView.addJavascriptInterface(this, "app") |
||||||
|
upWebViewTheme() |
||||||
|
binding.webView.setOnLongClickListener { |
||||||
|
val hitTestResult = binding.webView.hitTestResult |
||||||
|
if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || |
||||||
|
hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE |
||||||
|
) { |
||||||
|
hitTestResult.extra?.let { |
||||||
|
saveImage(it) |
||||||
|
return@setOnLongClickListener true |
||||||
|
} |
||||||
|
} |
||||||
|
return@setOnLongClickListener false |
||||||
|
} |
||||||
|
binding.webView.setDownloadListener { url, _, contentDisposition, _, _ -> |
||||||
|
val fileName = URLUtil.guessFileName(url, contentDisposition, null) |
||||||
|
binding.llView.longSnackbar(fileName, getString(R.string.action_download)) { |
||||||
|
Download.start(this, url, fileName) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun upWebViewTheme() { |
||||||
|
if (AppConfig.isNightTheme) { |
||||||
|
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { |
||||||
|
WebSettingsCompat.setForceDarkStrategy( |
||||||
|
binding.webView.settings, |
||||||
|
WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING |
||||||
|
) |
||||||
|
} |
||||||
|
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { |
||||||
|
WebSettingsCompat.setForceDark( |
||||||
|
binding.webView.settings, |
||||||
|
WebSettingsCompat.FORCE_DARK_ON |
||||||
|
) |
||||||
|
} else { |
||||||
|
binding.webView |
||||||
|
.evaluateJavascript(AppConst.darkWebViewJs, null) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun saveImage(webPic: String) { |
||||||
|
this.webPic = webPic |
||||||
|
val path = ACache.get(this).getAsString(imagePathKey) |
||||||
|
if (path.isNullOrEmpty()) { |
||||||
|
selectSaveFolder() |
||||||
|
} else { |
||||||
|
viewModel.saveImage(webPic, path) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun selectSaveFolder() { |
||||||
|
val default = arrayListOf<SelectItem<Int>>() |
||||||
|
val path = ACache.get(this).getAsString(imagePathKey) |
||||||
|
if (!path.isNullOrEmpty()) { |
||||||
|
default.add(SelectItem(path, -1)) |
||||||
|
} |
||||||
|
saveImage.launch { |
||||||
|
otherActions = default |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inner class CustomWebChromeClient : WebChromeClient() { |
||||||
|
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { |
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR |
||||||
|
binding.llView.invisible() |
||||||
|
binding.customWebView.addView(view) |
||||||
|
customWebViewCallback = callback |
||||||
|
} |
||||||
|
|
||||||
|
override fun onHideCustomView() { |
||||||
|
binding.customWebView.removeAllViews() |
||||||
|
binding.llView.visible() |
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inner class CustomWebViewClient : WebViewClient() { |
||||||
|
override fun shouldOverrideUrlLoading( |
||||||
|
view: WebView?, |
||||||
|
request: WebResourceRequest? |
||||||
|
): Boolean { |
||||||
|
request?.let { |
||||||
|
return shouldOverrideUrlLoading(it.url) |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("DEPRECATION") |
||||||
|
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { |
||||||
|
url?.let { |
||||||
|
return shouldOverrideUrlLoading(Uri.parse(it)) |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) { |
||||||
|
super.onPageFinished(view, url) |
||||||
|
upWebViewTheme() |
||||||
|
} |
||||||
|
|
||||||
|
private fun shouldOverrideUrlLoading(url: Uri): Boolean { |
||||||
|
when (url.scheme) { |
||||||
|
"http", "https" -> { |
||||||
|
return false |
||||||
|
} |
||||||
|
"legado", "yuedu" -> { |
||||||
|
startActivity<OnLineImportActivity> { |
||||||
|
data = url |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
else -> { |
||||||
|
binding.root.longSnackbar("跳转其它应用", "确认") { |
||||||
|
openUrl(url) |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package io.legado.app.ui.web |
||||||
|
|
||||||
|
import android.app.Application |
||||||
|
import android.net.Uri |
||||||
|
import android.util.Base64 |
||||||
|
import android.webkit.URLUtil |
||||||
|
import androidx.documentfile.provider.DocumentFile |
||||||
|
import io.legado.app.base.BaseViewModel |
||||||
|
import io.legado.app.constant.AppConst |
||||||
|
import io.legado.app.help.http.newCall |
||||||
|
import io.legado.app.help.http.okHttpClient |
||||||
|
import io.legado.app.utils.* |
||||||
|
import java.io.File |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
class WebViewModel(application: Application) : BaseViewModel(application) { |
||||||
|
|
||||||
|
|
||||||
|
fun saveImage(webPic: String?, path: String) { |
||||||
|
webPic ?: return |
||||||
|
execute { |
||||||
|
val fileName = "${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg" |
||||||
|
webData2bitmap(webPic)?.let { biteArray -> |
||||||
|
if (path.isContentScheme()) { |
||||||
|
val uri = Uri.parse(path) |
||||||
|
DocumentFile.fromTreeUri(context, uri)?.let { doc -> |
||||||
|
DocumentUtils.createFileIfNotExist(doc, fileName) |
||||||
|
?.writeBytes(context, biteArray) |
||||||
|
} |
||||||
|
} else { |
||||||
|
val file = FileUtils.createFileIfNotExist(File(path), fileName) |
||||||
|
file.writeBytes(biteArray) |
||||||
|
} |
||||||
|
} ?: throw Throwable("NULL") |
||||||
|
}.onError { |
||||||
|
context.toastOnUi("保存图片失败:${it.localizedMessage}") |
||||||
|
}.onSuccess { |
||||||
|
context.toastOnUi("保存成功") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private suspend fun webData2bitmap(data: String): ByteArray? { |
||||||
|
return if (URLUtil.isValidUrl(data)) { |
||||||
|
@Suppress("BlockingMethodInNonBlockingContext") |
||||||
|
okHttpClient.newCall { |
||||||
|
url(data) |
||||||
|
}.bytes() |
||||||
|
} else { |
||||||
|
Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:id="@+id/ll_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<io.legado.app.ui.widget.TitleBar |
||||||
|
android:id="@+id/title_bar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:fitStatusBar="false" /> |
||||||
|
|
||||||
|
<io.legado.app.ui.rss.read.VisibleWebView |
||||||
|
android:id="@+id/web_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/custom_web_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
|
||||||
|
</FrameLayout> |
Loading…
Reference in new issue