diff --git a/app/src/main/java/io/legado/app/ui/dict/DictSheet.kt b/app/src/main/java/io/legado/app/ui/dict/DictSheet.kt index 387c0858f..65ae2159d 100644 --- a/app/src/main/java/io/legado/app/ui/dict/DictSheet.kt +++ b/app/src/main/java/io/legado/app/ui/dict/DictSheet.kt @@ -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(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("""]*src\s*=\s*["']([^"']+)["'][^>]*/?>""") + +private fun parseHtmlBlocks(html: String): List { + val blocks = mutableListOf() + 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 }