fix: 字典弹窗使用Compose实现

This commit is contained in:
HapeLee
2026-06-15 23:41:21 +08:00
parent 6dc79761f6
commit ae56520750
@@ -1,33 +1,30 @@
package io.legado.app.ui.dict
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import io.legado.app.R
import io.legado.app.help.GlideImageGetter
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.utils.setHtml
import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.koinViewModel
@@ -147,33 +144,57 @@ private fun DictContent(
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
private fun DictHtmlContent(
htmlContent: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
var glideImageGetter by remember { mutableStateOf<GlideImageGetter?>(null) }
DisposableEffect(Unit) {
onDispose {
glideImageGetter?.clear()
glideImageGetter = null
val blocks = remember(htmlContent) { parseHtmlBlocks(htmlContent) }
Column(modifier = modifier.padding(horizontal = 32.dp, vertical = 16.dp)) {
blocks.forEach { block ->
when (block) {
is HtmlBlock.Text -> {
AppText(
text = remember(block.content) {
AnnotatedString.fromHtml(block.content)
},
)
}
is HtmlBlock.Image -> {
AsyncImage(
model = block.url,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
)
}
}
}
}
}
AndroidView(
factory = { ctx ->
TextView(ctx).apply {
movementMethod = LinkMovementMethod()
setPadding(32, 16, 32, 16)
}
},
update = { textView ->
glideImageGetter?.clear()
glideImageGetter = GlideImageGetter.create(context, textView, htmlContent)
textView.setHtml(htmlContent, glideImageGetter)
},
modifier = modifier,
)
private sealed interface HtmlBlock {
data class Text(val content: String) : HtmlBlock
data class Image(val url: String) : HtmlBlock
}
private val IMG_REGEX = Regex("""<img\s+[^>]*src\s*=\s*["']([^"']+)["'][^>]*/?>""")
private fun parseHtmlBlocks(html: String): List<HtmlBlock> {
val blocks = mutableListOf<HtmlBlock>()
var lastIndex = 0
IMG_REGEX.findAll(html).forEach { match ->
if (match.range.first > lastIndex) {
val textPart = html.substring(lastIndex, match.range.first).trim()
if (textPart.isNotBlank()) blocks.add(HtmlBlock.Text(textPart))
}
blocks.add(HtmlBlock.Image(match.groupValues[1]))
lastIndex = match.range.last + 1
}
if (lastIndex < html.length) {
val remaining = html.substring(lastIndex).trim()
if (remaining.isNotBlank()) blocks.add(HtmlBlock.Text(remaining))
}
return blocks
}