fix: 字典弹窗使用Compose实现

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