ganfra
2 years ago
4 changed files with 319 additions and 277 deletions
@ -0,0 +1,310 @@ |
|||||||
|
package io.element.android.x.features.messages.components.html |
||||||
|
|
||||||
|
import androidx.compose.foundation.background |
||||||
|
import androidx.compose.foundation.layout.Box |
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.offset |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||||
|
import androidx.compose.material3.ColorScheme |
||||||
|
import androidx.compose.material3.MaterialTheme |
||||||
|
import androidx.compose.material3.Surface |
||||||
|
import androidx.compose.material3.Text |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.draw.drawBehind |
||||||
|
import androidx.compose.ui.geometry.Offset |
||||||
|
import androidx.compose.ui.graphics.Color |
||||||
|
import androidx.compose.ui.text.* |
||||||
|
import androidx.compose.ui.text.font.FontFamily |
||||||
|
import androidx.compose.ui.text.font.FontStyle |
||||||
|
import androidx.compose.ui.text.font.FontWeight |
||||||
|
import androidx.compose.ui.text.style.TextDecoration |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.ui.unit.sp |
||||||
|
import org.jsoup.nodes.Document |
||||||
|
import org.jsoup.nodes.Element |
||||||
|
import org.jsoup.nodes.Node |
||||||
|
import org.jsoup.nodes.TextNode |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun HtmlDocument(document: Document, modifier: Modifier = Modifier) { |
||||||
|
HtmlBody(body = document.body(), modifier = modifier) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlBody(body: Element, modifier: Modifier = Modifier) { |
||||||
|
Column( |
||||||
|
modifier = modifier |
||||||
|
) { |
||||||
|
for (node in body.childNodes()) { |
||||||
|
when (node) { |
||||||
|
is TextNode -> { |
||||||
|
if (!node.isBlank) { |
||||||
|
Text(text = node.text()) |
||||||
|
} |
||||||
|
} |
||||||
|
is Element -> { |
||||||
|
HtmlBlock(element = node) |
||||||
|
} |
||||||
|
else -> { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlBlock(element: Element, modifier: Modifier = Modifier) { |
||||||
|
val blockModifier = modifier |
||||||
|
.padding(top = 4.dp) |
||||||
|
when (element.normalName()) { |
||||||
|
"p" -> HtmlParagraph(element, blockModifier) |
||||||
|
"h1", "h2", "h3", "h4", "h5", "h6" -> HtmlHeading(element, blockModifier) |
||||||
|
"ol" -> HtmlOrderedList(element, blockModifier) |
||||||
|
"ul" -> HtmlUnorderedList(element, blockModifier) |
||||||
|
"blockquote" -> HtmlBlockquote(element, blockModifier) |
||||||
|
"pre" -> HtmlPreformatted(element, blockModifier) |
||||||
|
"mx-reply" -> HtmlMxReply(element, blockModifier) |
||||||
|
// fallback to html inline |
||||||
|
else -> HtmlInline(element, modifier) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlInline(element: Element, modifier: Modifier = Modifier) { |
||||||
|
Box(modifier.padding(start = 8.dp)) { |
||||||
|
val styledText = buildAnnotatedString { |
||||||
|
appendInlineElement(element, MaterialTheme.colorScheme) |
||||||
|
} |
||||||
|
Text(styledText) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlPreformatted(pre: Element, modifier: Modifier = Modifier) { |
||||||
|
val isCode = pre.firstElementChild()?.normalName() == "code" |
||||||
|
val backgroundColor = |
||||||
|
if (isCode) MaterialTheme.colorScheme.codeBackground() else Color.Unspecified |
||||||
|
Box(modifier.background(color = backgroundColor)) { |
||||||
|
Text( |
||||||
|
text = pre.wholeText(), |
||||||
|
style = TextStyle(fontFamily = FontFamily.Monospace), |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlParagraph(paragraph: Element, modifier: Modifier = Modifier) { |
||||||
|
Box(modifier) { |
||||||
|
val styledText = buildAnnotatedString { |
||||||
|
appendInlineChildrenElements(paragraph.childNodes(), MaterialTheme.colorScheme) |
||||||
|
} |
||||||
|
Text(styledText) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlBlockquote(blockquote: Element, modifier: Modifier = Modifier) { |
||||||
|
val color = MaterialTheme.colorScheme.onBackground |
||||||
|
Box( |
||||||
|
modifier = modifier |
||||||
|
.drawBehind { |
||||||
|
drawLine( |
||||||
|
color = color, |
||||||
|
strokeWidth = 2f, |
||||||
|
start = Offset(12.dp.value, 0f), |
||||||
|
end = Offset(12.dp.value, size.height) |
||||||
|
) |
||||||
|
} |
||||||
|
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp) |
||||||
|
) { |
||||||
|
val text = buildAnnotatedString { |
||||||
|
withStyle(style = SpanStyle(fontStyle = FontStyle.Italic)) { |
||||||
|
appendInlineChildrenElements(blockquote.childNodes(), MaterialTheme.colorScheme) |
||||||
|
} |
||||||
|
} |
||||||
|
Text(text) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlHeading(heading: Element, modifier: Modifier = Modifier) { |
||||||
|
val style = when (heading.normalName()) { |
||||||
|
"h1" -> MaterialTheme.typography.headlineLarge.copy(fontSize = 30.sp) |
||||||
|
"h2" -> MaterialTheme.typography.headlineLarge.copy(fontSize = 26.sp) |
||||||
|
"h3" -> MaterialTheme.typography.headlineMedium.copy(fontSize = 22.sp) |
||||||
|
"h4" -> MaterialTheme.typography.headlineMedium.copy(fontSize = 18.sp) |
||||||
|
"h5" -> MaterialTheme.typography.headlineSmall.copy(fontSize = 14.sp) |
||||||
|
"h6" -> MaterialTheme.typography.headlineSmall.copy(fontSize = 12.sp) |
||||||
|
else -> { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
Box(modifier) { |
||||||
|
val text = buildAnnotatedString { |
||||||
|
appendInlineChildrenElements(heading.childNodes(), MaterialTheme.colorScheme) |
||||||
|
} |
||||||
|
Text(text, style = style) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlMxReply(mxReply: Element, modifier: Modifier = Modifier) { |
||||||
|
val blockquote = mxReply.childNodes().firstOrNull() ?: return |
||||||
|
val shape = RoundedCornerShape(12.dp) |
||||||
|
Surface( |
||||||
|
modifier = modifier.offset(x = -(8.dp)), |
||||||
|
color = MaterialTheme.colorScheme.background, |
||||||
|
shape = shape, |
||||||
|
) { |
||||||
|
val text = buildAnnotatedString { |
||||||
|
for (blockquoteNode in blockquote.childNodes()) { |
||||||
|
when (blockquoteNode) { |
||||||
|
is TextNode -> { |
||||||
|
withStyle( |
||||||
|
style = SpanStyle( |
||||||
|
fontSize = 12.sp, |
||||||
|
color = MaterialTheme.colorScheme.secondary |
||||||
|
) |
||||||
|
) { |
||||||
|
append(blockquoteNode.text()) |
||||||
|
} |
||||||
|
} |
||||||
|
is Element -> { |
||||||
|
when (blockquoteNode.normalName()) { |
||||||
|
"br" -> { |
||||||
|
append('\n') |
||||||
|
} |
||||||
|
"a" -> { |
||||||
|
append(blockquoteNode.ownText()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Text(text, modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlOrderedList(unorderedList: Element, modifier: Modifier = Modifier) { |
||||||
|
var number = 1 |
||||||
|
val delimiter = "." |
||||||
|
HtmlListItems(unorderedList, modifier = modifier) { |
||||||
|
val text = buildAnnotatedString { |
||||||
|
append("${number++}$delimiter ${it.text()}") |
||||||
|
} |
||||||
|
Text(text) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlUnorderedList(unorderedList: Element, modifier: Modifier = Modifier) { |
||||||
|
val marker = "・" |
||||||
|
HtmlListItems(unorderedList, modifier = modifier) { |
||||||
|
val text = buildAnnotatedString { |
||||||
|
append("$marker ${it.text()}") |
||||||
|
} |
||||||
|
Text(text) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun HtmlListItems( |
||||||
|
list: Element, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
content: @Composable (node: TextNode) -> Unit |
||||||
|
) { |
||||||
|
Column(modifier = modifier) { |
||||||
|
for (node in list.children()) { |
||||||
|
for (innerNode in node.childNodes()) { |
||||||
|
when (innerNode) { |
||||||
|
is TextNode -> { |
||||||
|
if (!innerNode.isBlank) content(innerNode) |
||||||
|
} |
||||||
|
is Element -> HtmlBlock( |
||||||
|
element = innerNode, |
||||||
|
modifier = modifier.padding(start = 4.dp) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun ColorScheme.codeBackground(): Color { |
||||||
|
return background.copy(alpha = 0.3f) |
||||||
|
} |
||||||
|
|
||||||
|
private fun AnnotatedString.Builder.appendInlineChildrenElements( |
||||||
|
childNodes: List<Node>, |
||||||
|
colors: ColorScheme |
||||||
|
) { |
||||||
|
|
||||||
|
for (node in childNodes) { |
||||||
|
when (node) { |
||||||
|
is TextNode -> { |
||||||
|
append(node.text()) |
||||||
|
} |
||||||
|
is Element -> { |
||||||
|
appendInlineElement(node, colors) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private fun AnnotatedString.Builder.appendInlineElement(element: Element, colors: ColorScheme) { |
||||||
|
when (element.normalName()) { |
||||||
|
"br" -> { |
||||||
|
append('\n') |
||||||
|
} |
||||||
|
"code" -> { |
||||||
|
withStyle( |
||||||
|
style = TextStyle( |
||||||
|
fontFamily = FontFamily.Monospace, |
||||||
|
background = colors.codeBackground() |
||||||
|
).toSpanStyle() |
||||||
|
) { |
||||||
|
appendInlineChildrenElements(element.childNodes(), colors) |
||||||
|
} |
||||||
|
} |
||||||
|
"del" -> { |
||||||
|
withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough)) { |
||||||
|
appendInlineChildrenElements(element.childNodes(), colors) |
||||||
|
} |
||||||
|
} |
||||||
|
"em" -> { |
||||||
|
withStyle(style = SpanStyle(fontStyle = FontStyle.Italic)) { |
||||||
|
appendInlineChildrenElements(element.childNodes(), colors) |
||||||
|
} |
||||||
|
} |
||||||
|
"strong" -> { |
||||||
|
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { |
||||||
|
appendInlineChildrenElements(element.childNodes(), colors) |
||||||
|
} |
||||||
|
} |
||||||
|
"a" -> { |
||||||
|
val href = element.attr("href") |
||||||
|
pushStringAnnotation(tag = "url", annotation = href) |
||||||
|
withStyle( |
||||||
|
style = SpanStyle( |
||||||
|
color = Color.Blue, |
||||||
|
textDecoration = TextDecoration.Underline |
||||||
|
) |
||||||
|
) { |
||||||
|
append(element.ownText()) |
||||||
|
} |
||||||
|
pop() |
||||||
|
} |
||||||
|
else -> { |
||||||
|
appendInlineChildrenElements(element.childNodes(), colors) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,269 +0,0 @@ |
|||||||
package io.element.android.x.features.messages.html |
|
||||||
|
|
||||||
import androidx.compose.foundation.Image |
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth |
|
||||||
import androidx.compose.foundation.layout.padding |
|
||||||
import androidx.compose.foundation.text.InlineTextContent |
|
||||||
import androidx.compose.material.MaterialTheme |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.runtime.remember |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.graphics.Color |
|
||||||
import androidx.compose.ui.input.pointer.pointerInput |
|
||||||
import androidx.compose.ui.platform.LocalUriHandler |
|
||||||
import androidx.compose.ui.text.* |
|
||||||
import androidx.compose.ui.text.font.FontStyle |
|
||||||
import androidx.compose.ui.text.font.FontWeight |
|
||||||
import androidx.compose.ui.text.style.TextDecoration |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
import coil.compose.rememberImagePainter |
|
||||||
import org.jsoup.nodes.Document |
|
||||||
import org.jsoup.nodes.Element |
|
||||||
import org.jsoup.nodes.Node |
|
||||||
import org.jsoup.nodes.TextNode |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlDocument(document: Document, modifier: Modifier = Modifier) { |
|
||||||
HtmlBody(body = document.body(), modifier = modifier) |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlBody(body: Element, modifier: Modifier = Modifier) { |
|
||||||
Column( |
|
||||||
modifier = modifier |
|
||||||
) { |
|
||||||
for (node in body.childNodes()) { |
|
||||||
when (node) { |
|
||||||
is TextNode -> { |
|
||||||
if (!node.isBlank) { |
|
||||||
Text( |
|
||||||
text = node.text(), |
|
||||||
style = MaterialTheme.typography.body1 |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
is Element -> { |
|
||||||
HtmlBlock(node) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlBlock(element: Element, modifier: Modifier = Modifier) { |
|
||||||
val blockModifier = modifier |
|
||||||
.fillMaxWidth() |
|
||||||
.padding(top = 4.dp) |
|
||||||
when (element.normalName()) { |
|
||||||
"p" -> HtmlParagraph(element, blockModifier) |
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6" -> HtmlHeading(heading = element, blockModifier) |
|
||||||
"ol" -> HtmlOrderedList(element, blockModifier) |
|
||||||
"ul" -> HtmlUnorderedList(element, blockModifier) |
|
||||||
"blockquote" -> Column { |
|
||||||
for (e in element.children()) { |
|
||||||
HtmlBlock(element = e) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlHeading(heading: Element, modifier: Modifier = Modifier) { |
|
||||||
val style = when (heading.normalName()) { |
|
||||||
"h1" -> MaterialTheme.typography.h1 |
|
||||||
"h2" -> MaterialTheme.typography.h2 |
|
||||||
"h3" -> MaterialTheme.typography.h3 |
|
||||||
"h4" -> MaterialTheme.typography.h4 |
|
||||||
"h5" -> MaterialTheme.typography.h5 |
|
||||||
"h6" -> MaterialTheme.typography.h6 |
|
||||||
else -> { |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
Box(modifier) { |
|
||||||
val text = buildAnnotatedString { |
|
||||||
appendInlineChildrenElements(heading.childNodes()) |
|
||||||
} |
|
||||||
HtmlText(text, style) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun HtmlOrderedList(unorderedList: Element, modifier: Modifier = Modifier) { |
|
||||||
var number = 0 |
|
||||||
val delimiter = "." |
|
||||||
HtmlListItems(unorderedList, modifier = modifier) { |
|
||||||
val text = buildAnnotatedString { |
|
||||||
pushStyle(MaterialTheme.typography.body1.toSpanStyle()) |
|
||||||
append("${number++}$delimiter ") |
|
||||||
appendInlineElements(it) |
|
||||||
pop() |
|
||||||
} |
|
||||||
HtmlText(text, MaterialTheme.typography.body1, modifier) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun HtmlUnorderedList(unorderedList: Element, modifier: Modifier = Modifier) { |
|
||||||
val marker = "-" |
|
||||||
HtmlListItems(unorderedList, modifier = modifier) { |
|
||||||
val text = buildAnnotatedString { |
|
||||||
pushStyle(MaterialTheme.typography.body1.toSpanStyle()) |
|
||||||
append("$marker ") |
|
||||||
appendInlineElements(it) |
|
||||||
pop() |
|
||||||
} |
|
||||||
HtmlText(text, MaterialTheme.typography.body1, modifier) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlListItems( |
|
||||||
list: Element, |
|
||||||
modifier: Modifier = Modifier, |
|
||||||
content: @Composable (node: Element) -> Unit |
|
||||||
) { |
|
||||||
if (list.children().isEmpty()) return |
|
||||||
Column(modifier = modifier) { |
|
||||||
val children = list.children().iterator() |
|
||||||
var listItem = children.next() |
|
||||||
while (listItem != null) { |
|
||||||
val innerChildren = listItem.children().iterator() |
|
||||||
var child = if (innerChildren.hasNext()) { |
|
||||||
innerChildren.next() |
|
||||||
} else { |
|
||||||
null |
|
||||||
} |
|
||||||
while (child != null) { |
|
||||||
when (child.normalName()) { |
|
||||||
"ul" -> HtmlUnorderedList(child, modifier) |
|
||||||
"ol" -> HtmlOrderedList(child, modifier) |
|
||||||
else -> content(child) |
|
||||||
} |
|
||||||
child = if (innerChildren.hasNext()) { |
|
||||||
innerChildren.next() |
|
||||||
} else { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
listItem = if (children.hasNext()) { |
|
||||||
children.next() |
|
||||||
} else { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun AnnotatedString.Builder.appendInlineChildrenElements(childNodes: List<Node>) { |
|
||||||
for (node in childNodes) { |
|
||||||
when (node) { |
|
||||||
is TextNode -> { |
|
||||||
append(node.text()) |
|
||||||
} |
|
||||||
is Element -> { |
|
||||||
appendInlineElements(node) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun AnnotatedString.Builder.appendInlineElements(element: Element) { |
|
||||||
when (element.normalName()) { |
|
||||||
"br" -> { |
|
||||||
append('\n') |
|
||||||
} |
|
||||||
"del" -> { |
|
||||||
withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough)) { |
|
||||||
appendInlineChildrenElements(element.childNodes()) |
|
||||||
} |
|
||||||
} |
|
||||||
"em" -> { |
|
||||||
withStyle(style = SpanStyle(fontStyle = FontStyle.Italic)) { |
|
||||||
appendInlineChildrenElements(element.childNodes()) |
|
||||||
} |
|
||||||
} |
|
||||||
"strong" -> { |
|
||||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { |
|
||||||
appendInlineChildrenElements(element.childNodes()) |
|
||||||
} |
|
||||||
} |
|
||||||
"a" -> { |
|
||||||
val href = element.attr("href") |
|
||||||
pushStringAnnotation(tag = "url", annotation = href) |
|
||||||
withStyle( |
|
||||||
style = SpanStyle( |
|
||||||
color = Color.Blue, |
|
||||||
textDecoration = TextDecoration.Underline |
|
||||||
) |
|
||||||
) { |
|
||||||
append(element.ownText()) |
|
||||||
} |
|
||||||
pop() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun HtmlText(text: AnnotatedString, style: TextStyle, modifier: Modifier = Modifier) { |
|
||||||
val uriHandler = LocalUriHandler.current |
|
||||||
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) } |
|
||||||
Text(text = text, |
|
||||||
modifier.pointerInput(Unit) { |
|
||||||
detectTapGestures { offset -> |
|
||||||
layoutResult.value?.let { layoutResult -> |
|
||||||
val position = layoutResult.getOffsetForPosition(offset) |
|
||||||
text.getStringAnnotations(position, position) |
|
||||||
.firstOrNull() |
|
||||||
?.let { sa -> |
|
||||||
if (sa.tag == "url") { |
|
||||||
uriHandler.openUri(sa.item) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
style = style, |
|
||||||
inlineContent = mapOf( |
|
||||||
"imageUrl" to InlineTextContent( |
|
||||||
Placeholder(style.fontSize, style.fontSize, PlaceholderVerticalAlign.Bottom) |
|
||||||
) { |
|
||||||
Image( |
|
||||||
painter = rememberImagePainter( |
|
||||||
data = it, |
|
||||||
), |
|
||||||
contentDescription = null, |
|
||||||
modifier = modifier, |
|
||||||
alignment = Alignment.Center |
|
||||||
) |
|
||||||
|
|
||||||
} |
|
||||||
), |
|
||||||
onTextLayout = { layoutResult.value = it } |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun HtmlParagraph(paragraph: Element, modifier: Modifier = Modifier) { |
|
||||||
Box(modifier) { |
|
||||||
val styledText = buildAnnotatedString { |
|
||||||
pushStyle(MaterialTheme.typography.body1.toSpanStyle()) |
|
||||||
appendInlineChildrenElements(paragraph.childNodes()) |
|
||||||
pop() |
|
||||||
} |
|
||||||
HtmlText(styledText, MaterialTheme.typography.body1) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue