feat: render image in chat message #36

Merged
reya merged 5 commits from feat/render-image into master 2026-07-04 02:25:10 +00:00
Showing only changes of commit b29d0f522c - Show all commits

View File

@@ -74,8 +74,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
@@ -558,11 +563,8 @@ fun ChatMessage(
) { ) {
val content = rumor.content() val content = rumor.content()
val images = remember(content) { content.extractUrls().filter { it.isImageUrl() } } val images = remember(content) { content.extractUrls().filter { it.isImageUrl() } }
val cleanedContent = remember(content) { content.removeImageUrls() }
val timestamp = remember(rumor) { rumor.createdAt().formatAsTime() } val timestamp = remember(rumor) { rumor.createdAt().formatAsTime() }
var isMessageClicked by remember { mutableStateOf(false) }
val bubbleShape = if (isMine) { val bubbleShape = if (isMine) {
RoundedCornerShape(topStart = 20.dp, topEnd = 4.dp, bottomStart = 20.dp, bottomEnd = 20.dp) RoundedCornerShape(topStart = 20.dp, topEnd = 4.dp, bottomStart = 20.dp, bottomEnd = 20.dp)
} else { } else {
@@ -575,6 +577,36 @@ fun ChatMessage(
val contentColor = val contentColor =
if (!isMine) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onPrimaryContainer if (!isMine) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onPrimaryContainer
val annotatedContent = remember(content, contentColor) {
buildAnnotatedString {
val cleanedContent = content.removeImageUrls()
val urlRegex = Regex("(https?://\\S+)", RegexOption.IGNORE_CASE)
var lastIndex = 0
urlRegex.findAll(cleanedContent).forEach { matchResult ->
append(cleanedContent.substring(lastIndex, matchResult.range.first))
val url = matchResult.value
pushLink(
LinkAnnotation.Url(
url = url,
styles = TextLinkStyles(
style = SpanStyle(
color = contentColor,
textDecoration = TextDecoration.Underline,
fontWeight = FontWeight.Medium
)
)
)
)
append(url)
pop()
lastIndex = matchResult.range.last + 1
}
append(cleanedContent.substring(lastIndex))
}
}
var isMessageClicked by remember { mutableStateOf(false) }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -591,7 +623,7 @@ fun ChatMessage(
horizontalAlignment = if (isMine) Alignment.End else Alignment.Start, horizontalAlignment = if (isMine) Alignment.End else Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
if (cleanedContent.isNotBlank()) { if (annotatedContent.isNotBlank()) {
Surface( Surface(
color = containerColor, color = containerColor,
contentColor = contentColor, contentColor = contentColor,
@@ -599,7 +631,7 @@ fun ChatMessage(
modifier = Modifier.widthIn(max = 280.dp) modifier = Modifier.widthIn(max = 280.dp)
) { ) {
Text( Text(
text = rumor.content(), text = annotatedContent,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
style = MaterialTheme.typography.bodyLarge style = MaterialTheme.typography.bodyLarge
) )