|
@@ -1,9 +1,17 @@
|
|
|
package io.legado.app.ui.book.info
|
|
package io.legado.app.ui.book.info
|
|
|
|
|
|
|
|
|
|
+import android.annotation.SuppressLint
|
|
|
|
|
+import android.content.Context
|
|
|
import android.content.Intent
|
|
import android.content.Intent
|
|
|
import android.graphics.Bitmap
|
|
import android.graphics.Bitmap
|
|
|
import android.net.Uri
|
|
import android.net.Uri
|
|
|
|
|
+import android.os.Message
|
|
|
|
|
+import android.view.MotionEvent
|
|
|
|
|
+import android.view.View
|
|
|
|
|
+import android.view.ViewGroup
|
|
|
|
|
+import android.webkit.WebChromeClient
|
|
|
import android.webkit.WebResourceRequest
|
|
import android.webkit.WebResourceRequest
|
|
|
|
|
+import android.webkit.WebSettings
|
|
|
import android.webkit.WebView
|
|
import android.webkit.WebView
|
|
|
import android.webkit.WebViewClient
|
|
import android.webkit.WebViewClient
|
|
|
import androidx.compose.animation.AnimatedVisibilityScope
|
|
import androidx.compose.animation.AnimatedVisibilityScope
|
|
@@ -75,11 +83,12 @@ import androidx.compose.ui.draw.clip
|
|
|
import androidx.compose.ui.graphics.Brush
|
|
import androidx.compose.ui.graphics.Brush
|
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.Color
|
|
|
import androidx.compose.ui.graphics.lerp
|
|
import androidx.compose.ui.graphics.lerp
|
|
|
|
|
+import androidx.compose.ui.graphics.toArgb
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
|
import androidx.compose.ui.layout.ContentScale
|
|
import androidx.compose.ui.layout.ContentScale
|
|
|
|
|
+import androidx.compose.ui.platform.LocalConfiguration
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
-import androidx.compose.ui.platform.LocalDensity
|
|
|
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.res.stringResource
|
|
|
import androidx.compose.ui.semantics.Role
|
|
import androidx.compose.ui.semantics.Role
|
|
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
|
@@ -87,6 +96,7 @@ import androidx.compose.ui.semantics.contentDescription
|
|
|
import androidx.compose.ui.semantics.role
|
|
import androidx.compose.ui.semantics.role
|
|
|
import androidx.compose.ui.semantics.semantics
|
|
import androidx.compose.ui.semantics.semantics
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
|
|
|
+import androidx.compose.ui.unit.Dp
|
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.dp
|
|
|
import androidx.compose.ui.viewinterop.AndroidView
|
|
import androidx.compose.ui.viewinterop.AndroidView
|
|
|
import coil3.ImageLoader
|
|
import coil3.ImageLoader
|
|
@@ -147,6 +157,7 @@ import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
|
|
|
import io.legado.app.ui.widget.components.topbar.miuixTopBarActionsEndPadding
|
|
import io.legado.app.ui.widget.components.topbar.miuixTopBarActionsEndPadding
|
|
|
import io.legado.app.ui.widget.components.topbar.miuixTopBarSlotPadding
|
|
import io.legado.app.ui.widget.components.topbar.miuixTopBarSlotPadding
|
|
|
import io.legado.app.ui.widget.components.variable.VariableEditorSheet
|
|
import io.legado.app.ui.widget.components.variable.VariableEditorSheet
|
|
|
|
|
+import io.legado.app.utils.ColorUtils
|
|
|
import io.legado.app.utils.HtmlFormatter
|
|
import io.legado.app.utils.HtmlFormatter
|
|
|
import io.legado.app.utils.openUrl
|
|
import io.legado.app.utils.openUrl
|
|
|
import kotlinx.collections.immutable.ImmutableList
|
|
import kotlinx.collections.immutable.ImmutableList
|
|
@@ -155,6 +166,7 @@ import kotlinx.coroutines.delay
|
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.launch
|
|
|
import org.koin.compose.koinInject
|
|
import org.koin.compose.koinInject
|
|
|
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
|
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
|
|
|
|
+import kotlin.math.abs
|
|
|
import io.legado.app.model.BookCover as BookCoverModel
|
|
import io.legado.app.model.BookCover as BookCoverModel
|
|
|
import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar
|
|
import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar
|
|
|
|
|
|
|
@@ -1401,13 +1413,13 @@ private sealed interface BookInfoIntroContent {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 解析简介前缀,与上游 showBookIntro 一致:
|
|
* 解析简介前缀,与上游 showBookIntro 一致:
|
|
|
- * 前缀 `<useweb>`/`<usehtml>`/`<md>` 后直到最后一个 `<` 之间的内容为待渲染文本;
|
|
|
|
|
|
|
+ * 前缀 `<useweb>`/`<usehtml>`/`<md>`(忽略大小写)后直到最后一个 `<` 之间的内容为待渲染文本;
|
|
|
* 前缀残缺时按纯文本回退。
|
|
* 前缀残缺时按纯文本回退。
|
|
|
*/
|
|
*/
|
|
|
private fun parseBookInfoIntro(intro: String?): BookInfoIntroContent? {
|
|
private fun parseBookInfoIntro(intro: String?): BookInfoIntroContent? {
|
|
|
if (intro.isNullOrBlank()) return null
|
|
if (intro.isNullOrBlank()) return null
|
|
|
return when {
|
|
return when {
|
|
|
- intro.startsWith("<useweb>") -> {
|
|
|
|
|
|
|
+ intro.startsWith("<useweb>", ignoreCase = true) -> {
|
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
|
if (lastIndex < 8) {
|
|
if (lastIndex < 8) {
|
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
@@ -1416,7 +1428,7 @@ private fun parseBookInfoIntro(intro: String?): BookInfoIntroContent? {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- intro.startsWith("<usehtml>") -> {
|
|
|
|
|
|
|
+ intro.startsWith("<usehtml>", ignoreCase = true) -> {
|
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
|
if (lastIndex < 9) {
|
|
if (lastIndex < 9) {
|
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
@@ -1425,7 +1437,7 @@ private fun parseBookInfoIntro(intro: String?): BookInfoIntroContent? {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- intro.startsWith("<md>") -> {
|
|
|
|
|
|
|
+ intro.startsWith("<md>", ignoreCase = true) -> {
|
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
val lastIndex = intro.lastIndexOf("<")
|
|
|
if (lastIndex < 4) {
|
|
if (lastIndex < 4) {
|
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
BookInfoIntroContent.Plain(HtmlFormatter.formatDisplayText(intro))
|
|
@@ -1444,6 +1456,7 @@ private fun parseBookInfoIntro(intro: String?): BookInfoIntroContent? {
|
|
|
* 并处理 legado/yuedu scheme(导入)与其他 scheme(确认后跳转)。
|
|
* 并处理 legado/yuedu scheme(导入)与其他 scheme(确认后跳转)。
|
|
|
*/
|
|
*/
|
|
|
@Composable
|
|
@Composable
|
|
|
|
|
+@SuppressLint("SetJavaScriptEnabled")
|
|
|
private fun BookInfoWebIntro(
|
|
private fun BookInfoWebIntro(
|
|
|
html: String,
|
|
html: String,
|
|
|
baseUrl: String?,
|
|
baseUrl: String?,
|
|
@@ -1451,55 +1464,45 @@ private fun BookInfoWebIntro(
|
|
|
onJumpToAnotherApp: (Uri) -> Unit,
|
|
onJumpToAnotherApp: (Uri) -> Unit,
|
|
|
) {
|
|
) {
|
|
|
val context = LocalContext.current
|
|
val context = LocalContext.current
|
|
|
- val density = LocalDensity.current
|
|
|
|
|
- var contentHeight by remember { mutableStateOf(0) }
|
|
|
|
|
|
|
+ // 初始高度先给一屏,否则 WebView 在 0 高度下不会被布局,也就无法测量内容高度
|
|
|
|
|
+ val pageHeight = LocalConfiguration.current.screenHeightDp.dp.coerceAtLeast(320.dp)
|
|
|
|
|
+ val textColor = LegadoTheme.colorScheme.onSurface
|
|
|
|
|
+ val textColorHex = remember(textColor) {
|
|
|
|
|
+ ColorUtils.intToString(textColor.toArgb())
|
|
|
|
|
+ }
|
|
|
|
|
+ val wrappedHtml = remember(html, textColorHex) {
|
|
|
|
|
+ buildBookInfoWebIntroHtml(html, textColorHex)
|
|
|
|
|
+ }
|
|
|
|
|
+ val loadKey = remember(baseUrl, wrappedHtml) { "${baseUrl.orEmpty()}\n$wrappedHtml" }
|
|
|
|
|
+ val contentHeightState = remember(loadKey) { mutableStateOf<Dp?>(null) }
|
|
|
|
|
+ val loadedKeyState = remember { mutableStateOf<String?>(null) }
|
|
|
|
|
+ val isCurrentLoad = { loadedKeyState.value == loadKey }
|
|
|
|
|
+ // evaluateJavascript 返回的是 CSS px,与 Compose dp 等值,不能再乘/除 density
|
|
|
|
|
+ val onCssHeight: (Float) -> Unit = { cssHeight ->
|
|
|
|
|
+ val next = cssHeight.dp
|
|
|
|
|
+ val current = contentHeightState.value
|
|
|
|
|
+ if (current == null || abs(current.value - next.value) > 8f) {
|
|
|
|
|
+ contentHeightState.value = next
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
val webView = remember(bookSource?.bookSourceUrl) {
|
|
val webView = remember(bookSource?.bookSourceUrl) {
|
|
|
WebView(context).apply {
|
|
WebView(context).apply {
|
|
|
setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
|
settings.apply {
|
|
settings.apply {
|
|
|
javaScriptEnabled = true
|
|
javaScriptEnabled = true
|
|
|
domStorageEnabled = true
|
|
domStorageEnabled = true
|
|
|
- useWideViewPort = true
|
|
|
|
|
- loadWithOverviewMode = true
|
|
|
|
|
- }
|
|
|
|
|
- webViewClient = object : WebViewClient() {
|
|
|
|
|
- override fun shouldOverrideUrlLoading(
|
|
|
|
|
- view: WebView?,
|
|
|
|
|
- request: WebResourceRequest?,
|
|
|
|
|
- ): Boolean {
|
|
|
|
|
- request?.url?.let { url ->
|
|
|
|
|
- return when (url.scheme) {
|
|
|
|
|
- "http", "https" -> false
|
|
|
|
|
- "legado", "yuedu" -> {
|
|
|
|
|
- context.startActivity(
|
|
|
|
|
- Intent(context, OnLineImportActivity::class.java).apply {
|
|
|
|
|
- data = url
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- else -> {
|
|
|
|
|
- onJumpToAnotherApp(url)
|
|
|
|
|
- true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return super.shouldOverrideUrlLoading(view, request)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
|
|
|
|
- super.onPageStarted(view, url, favicon)
|
|
|
|
|
- view?.evaluateJavascript(WebJsExtensions.getInjectionString, null)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onPageFinished(view: WebView?, url: String?) {
|
|
|
|
|
- super.onPageFinished(view, url)
|
|
|
|
|
- view?.post {
|
|
|
|
|
- contentHeight = view.contentHeight
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ loadsImagesAutomatically = true
|
|
|
|
|
+ blockNetworkImage = false
|
|
|
|
|
+ mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
|
|
|
|
+ mediaPlaybackRequiresUserGesture = false
|
|
|
|
|
+ builtInZoomControls = false
|
|
|
|
|
+ displayZoomControls = false
|
|
|
|
|
+ textZoom = 100
|
|
|
}
|
|
}
|
|
|
|
|
+ // useweb 简介可能是整页 HTML:禁用 overscroll 与滚动条,滚动交给外层 LazyColumn
|
|
|
|
|
+ overScrollMode = View.OVER_SCROLL_NEVER
|
|
|
|
|
+ isVerticalScrollBarEnabled = false
|
|
|
|
|
+ webChromeClient = buildBookInfoWebChromeClient()
|
|
|
addJavascriptInterface(WebCacheManager, WebJsExtensions.nameCache)
|
|
addJavascriptInterface(WebCacheManager, WebJsExtensions.nameCache)
|
|
|
bookSource?.let { source ->
|
|
bookSource?.let { source ->
|
|
|
addJavascriptInterface(source as BaseSource, WebJsExtensions.nameSource)
|
|
addJavascriptInterface(source as BaseSource, WebJsExtensions.nameSource)
|
|
@@ -1510,8 +1513,11 @@ private fun BookInfoWebIntro(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- DisposableEffect(Unit) {
|
|
|
|
|
|
|
+ DisposableEffect(webView) {
|
|
|
|
|
+ webView.onResume()
|
|
|
onDispose {
|
|
onDispose {
|
|
|
|
|
+ webView.stopLoading()
|
|
|
|
|
+ (webView.parent as? ViewGroup)?.removeView(webView)
|
|
|
webView.destroy()
|
|
webView.destroy()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1519,16 +1525,208 @@ private fun BookInfoWebIntro(
|
|
|
factory = { webView },
|
|
factory = { webView },
|
|
|
modifier = Modifier
|
|
modifier = Modifier
|
|
|
.fillMaxWidth()
|
|
.fillMaxWidth()
|
|
|
- .height(with(density) { contentHeight.toDp() }),
|
|
|
|
|
|
|
+ .height(contentHeightState.value ?: pageHeight),
|
|
|
update = { view ->
|
|
update = { view ->
|
|
|
- val loadedHtml = view.tag as? String
|
|
|
|
|
- if (loadedHtml != html) {
|
|
|
|
|
- view.tag = html
|
|
|
|
|
- view.loadDataWithBaseURL(baseUrl, html, "text/html", "utf-8", baseUrl)
|
|
|
|
|
|
|
+ view.webViewClient = buildBookInfoWebIntroClient(
|
|
|
|
|
+ context = context,
|
|
|
|
|
+ isCurrentLoad = isCurrentLoad,
|
|
|
|
|
+ onCssHeight = onCssHeight,
|
|
|
|
|
+ onJumpToAnotherApp = onJumpToAnotherApp,
|
|
|
|
|
+ )
|
|
|
|
|
+ view.setOnTouchListener { _, event ->
|
|
|
|
|
+ if (
|
|
|
|
|
+ event.action == MotionEvent.ACTION_UP ||
|
|
|
|
|
+ event.action == MotionEvent.ACTION_CANCEL
|
|
|
|
|
+ ) {
|
|
|
|
|
+ scheduleBookInfoWebIntroHeightMeasure(
|
|
|
|
|
+ webView = view,
|
|
|
|
|
+ isCurrentLoad = isCurrentLoad,
|
|
|
|
|
+ onCssHeight = onCssHeight,
|
|
|
|
|
+ delays = bookInfoWebIntroTouchMeasureDelays,
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ false
|
|
|
|
|
+ }
|
|
|
|
|
+ if (loadedKeyState.value != loadKey) {
|
|
|
|
|
+ loadedKeyState.value = loadKey
|
|
|
|
|
+ view.stopLoading()
|
|
|
|
|
+ view.loadDataWithBaseURL(baseUrl, wrappedHtml, "text/html", "utf-8", baseUrl)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * `<useweb>` 简介的 HTML 外壳:透明背景 + viewport + 主题文字色。
|
|
|
|
|
+ * 缺少 viewport 时 WebView 会按 980px 视口布局,简介排版会塌缩。
|
|
|
|
|
+ */
|
|
|
|
|
+private fun buildBookInfoWebIntroHtml(html: String, textColorHex: String): String = """
|
|
|
|
|
+ <html>
|
|
|
|
|
+ <head>
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
+ <style>
|
|
|
|
|
+ html, body {
|
|
|
|
|
+ background: transparent !important;
|
|
|
|
|
+ color: $textColorHex;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+ word-break: break-word;
|
|
|
|
|
+ -webkit-user-select: text !important;
|
|
|
|
|
+ user-select: text !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ body * {
|
|
|
|
|
+ -webkit-user-select: text !important;
|
|
|
|
|
+ user-select: text !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ img, video, iframe {
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+ </head>
|
|
|
|
|
+ <body>$html</body>
|
|
|
|
|
+ </html>
|
|
|
|
|
+""".trimIndent()
|
|
|
|
|
+
|
|
|
|
|
+private fun buildBookInfoWebIntroClient(
|
|
|
|
|
+ context: Context,
|
|
|
|
|
+ isCurrentLoad: () -> Boolean,
|
|
|
|
|
+ onCssHeight: (Float) -> Unit,
|
|
|
|
|
+ onJumpToAnotherApp: (Uri) -> Unit,
|
|
|
|
|
+): WebViewClient = object : WebViewClient() {
|
|
|
|
|
+
|
|
|
|
|
+ private val injectionString = WebJsExtensions.getInjectionString
|
|
|
|
|
+
|
|
|
|
|
+ override fun shouldOverrideUrlLoading(
|
|
|
|
|
+ view: WebView?,
|
|
|
|
|
+ request: WebResourceRequest?,
|
|
|
|
|
+ ): Boolean {
|
|
|
|
|
+ val url = request?.url ?: return super.shouldOverrideUrlLoading(view, request)
|
|
|
|
|
+ return when (url.scheme) {
|
|
|
|
|
+ "http", "https" -> false
|
|
|
|
|
+ "legado", "yuedu" -> {
|
|
|
|
|
+ context.startActivity(
|
|
|
|
|
+ Intent(context, OnLineImportActivity::class.java).apply {
|
|
|
|
|
+ data = url
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ else -> {
|
|
|
|
|
+ onJumpToAnotherApp(url)
|
|
|
|
|
+ true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
|
|
|
|
+ super.onPageStarted(view, url, favicon)
|
|
|
|
|
+ view?.let { runCatching { it.evaluateJavascript(injectionString, null) } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onPageFinished(view: WebView?, url: String?) {
|
|
|
|
|
+ super.onPageFinished(view, url)
|
|
|
|
|
+ val webView = view ?: return
|
|
|
|
|
+ runCatching { webView.evaluateJavascript(injectionString, null) }
|
|
|
|
|
+ scheduleBookInfoWebIntroHeightMeasure(
|
|
|
|
|
+ webView = webView,
|
|
|
|
|
+ isCurrentLoad = isCurrentLoad,
|
|
|
|
|
+ onCssHeight = onCssHeight,
|
|
|
|
|
+ delays = bookInfoWebIntroPageLoadMeasureDelays,
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@SuppressLint("SetJavaScriptEnabled")
|
|
|
|
|
+private fun buildBookInfoWebChromeClient(): WebChromeClient = object : WebChromeClient() {
|
|
|
|
|
+
|
|
|
|
|
+ override fun onCreateWindow(
|
|
|
|
|
+ view: WebView?,
|
|
|
|
|
+ isDialog: Boolean,
|
|
|
|
|
+ isUserGesture: Boolean,
|
|
|
|
|
+ resultMsg: Message?,
|
|
|
|
|
+ ): Boolean {
|
|
|
|
|
+ val transport = resultMsg?.obj as? WebView.WebViewTransport ?: return false
|
|
|
|
|
+ val host = view ?: return false
|
|
|
|
|
+ val popup = WebView(host.context).apply {
|
|
|
|
|
+ settings.javaScriptEnabled = true
|
|
|
|
|
+ webViewClient = object : WebViewClient() {
|
|
|
|
|
+ override fun shouldOverrideUrlLoading(
|
|
|
|
|
+ popupView: WebView?,
|
|
|
|
|
+ request: WebResourceRequest?,
|
|
|
|
|
+ ): Boolean {
|
|
|
|
|
+ request?.url?.let { host.loadUrl(it.toString()) }
|
|
|
|
|
+ popupView?.post { popupView.destroy() }
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ transport.webView = popup
|
|
|
|
|
+ resultMsg.sendToTarget()
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private val bookInfoWebIntroPageLoadMeasureDelays = longArrayOf(0L, 120L, 360L, 720L, 1200L)
|
|
|
|
|
+
|
|
|
|
|
+private val bookInfoWebIntroTouchMeasureDelays = longArrayOf(120L, 360L, 720L)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 读取文档真实高度(CSS px)。只统计可见子元素底部,避免 useweb 页面把 body 撑满视口
|
|
|
|
|
+ * 导致高度被拉到整屏。
|
|
|
|
|
+ */
|
|
|
|
|
+private val bookInfoWebIntroHeightJs = """
|
|
|
|
|
+ (function() {
|
|
|
|
|
+ var body = document.body;
|
|
|
|
|
+ var doc = document.documentElement;
|
|
|
|
|
+ var bottom = 0;
|
|
|
|
|
+ if (body) {
|
|
|
|
|
+ Array.prototype.forEach.call(body.children || [], function(el) {
|
|
|
|
|
+ var style = window.getComputedStyle ? window.getComputedStyle(el) : null;
|
|
|
|
|
+ if (style && (style.display === 'none' || style.visibility === 'hidden')) return;
|
|
|
|
|
+ var rect = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
|
|
|
|
|
+ if (!rect) return;
|
|
|
|
|
+ bottom = Math.max(bottom, rect.bottom + window.pageYOffset);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ var documentHeight = Math.max(
|
|
|
|
|
+ body ? body.scrollHeight || 0 : 0,
|
|
|
|
|
+ body ? body.offsetHeight || 0 : 0,
|
|
|
|
|
+ doc ? doc.scrollHeight || 0 : 0,
|
|
|
|
|
+ doc ? doc.offsetHeight || 0 : 0
|
|
|
|
|
+ );
|
|
|
|
|
+ return bottom > 1 ? bottom : documentHeight;
|
|
|
|
|
+ })();
|
|
|
|
|
+""".trimIndent()
|
|
|
|
|
+
|
|
|
|
|
+private fun scheduleBookInfoWebIntroHeightMeasure(
|
|
|
|
|
+ webView: WebView,
|
|
|
|
|
+ isCurrentLoad: () -> Boolean,
|
|
|
|
|
+ onCssHeight: (Float) -> Unit,
|
|
|
|
|
+ delays: LongArray,
|
|
|
|
|
+) {
|
|
|
|
|
+ if (!isCurrentLoad()) return
|
|
|
|
|
+ delays.forEach { delayMillis ->
|
|
|
|
|
+ webView.postDelayed({
|
|
|
|
|
+ if (!isCurrentLoad() || webView.handler == null || !webView.isAttachedToWindow) {
|
|
|
|
|
+ return@postDelayed
|
|
|
|
|
+ }
|
|
|
|
|
+ runCatching {
|
|
|
|
|
+ webView.evaluateJavascript(bookInfoWebIntroHeightJs) { result ->
|
|
|
|
|
+ if (!isCurrentLoad()) return@evaluateJavascript
|
|
|
|
|
+ val cssHeight = result?.trim()?.trim('"')?.toFloatOrNull()
|
|
|
|
|
+ ?: return@evaluateJavascript
|
|
|
|
|
+ if (cssHeight > 1f) {
|
|
|
|
|
+ onCssHeight(cssHeight)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, delayMillis)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
@Composable
|
|
@Composable
|
|
|
private fun BookInfoDialogs(
|
|
private fun BookInfoDialogs(
|
|
|
state: BookInfoUiState,
|
|
state: BookInfoUiState,
|