mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
@@ -105,7 +105,9 @@ data class Message(
|
|||||||
/** Blurhashes for image files (by index); from decrypted message JSON. */
|
/** Blurhashes for image files (by index); from decrypted message JSON. */
|
||||||
@kotlinx.serialization.Transient val fileThumbnails: List<String>? = null,
|
@kotlinx.serialization.Transient val fileThumbnails: List<String>? = null,
|
||||||
/** Aspect ratios (width/height) for image files (by index); from decrypted message JSON. */
|
/** Aspect ratios (width/height) for image files (by index); from decrypted message JSON. */
|
||||||
@kotlinx.serialization.Transient val fileAspectRatios: List<Float>? = null
|
@kotlinx.serialization.Transient val fileAspectRatios: List<Float>? = null,
|
||||||
|
/** File sizes in bytes (by index); from decrypted message JSON. */
|
||||||
|
@kotlinx.serialization.Transient val fileSizes: List<Long>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -7,17 +7,21 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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.Row
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AttachFile
|
import androidx.compose.material.icons.filled.AttachFile
|
||||||
import androidx.compose.material.icons.filled.Image
|
import androidx.compose.material.icons.filled.Download
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -34,8 +38,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.blur
|
import androidx.compose.ui.draw.blur
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil3.compose.rememberAsyncImagePainter
|
import coil3.compose.rememberAsyncImagePainter
|
||||||
import com.pr0gramm3r101.utils.conditional
|
import com.pr0gramm3r101.utils.conditional
|
||||||
@@ -63,6 +69,9 @@ fun AttachmentPreview(
|
|||||||
isUploading: Boolean,
|
isUploading: Boolean,
|
||||||
fileThumbnail: String? = null,
|
fileThumbnail: String? = null,
|
||||||
fileAspectRatio: Float? = null,
|
fileAspectRatio: Float? = null,
|
||||||
|
fileSizeBytes: Long? = null,
|
||||||
|
onFileClick: (() -> Unit)? = null,
|
||||||
|
isAuthor: Boolean = false,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val isImage = when {
|
val isImage = when {
|
||||||
@@ -77,53 +86,81 @@ fun AttachmentPreview(
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isFile = file != null && !isImage
|
||||||
|
val isImageWithThumb = file != null && isImage && dmEnvelope != null && !fileThumbnail.isNullOrBlank()
|
||||||
|
val isPendingImage = pendingFileUri != null && isImage
|
||||||
|
val isPendingFile = pendingFileUri != null && !isImage
|
||||||
|
|
||||||
|
when {
|
||||||
|
isFile -> {
|
||||||
|
FileIconContent(
|
||||||
|
filename = file!!.name,
|
||||||
|
sizeBytes = fileSizeBytes,
|
||||||
|
onClick = onFileClick,
|
||||||
|
isAuthor = isAuthor,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isPendingFile -> {
|
||||||
|
FileIconContent(
|
||||||
|
filename = "File",
|
||||||
|
sizeBytes = null,
|
||||||
|
onClick = null,
|
||||||
|
isAuthor = isAuthor,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isImageWithThumb -> {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.conditional(
|
.conditional(
|
||||||
fileAspectRatio != null && fileAspectRatio > 0f,
|
fileAspectRatio != null && fileAspectRatio!! > 0f,
|
||||||
`if` = {
|
`if` = {
|
||||||
Modifier
|
Modifier
|
||||||
.aspectRatio(fileAspectRatio!!)
|
.aspectRatio(fileAspectRatio!!)
|
||||||
.sizeIn(maxWidth = IMAGE_SIZE, maxHeight = IMAGE_SIZE)
|
.sizeIn(maxWidth = IMAGE_SIZE, maxHeight = IMAGE_SIZE)
|
||||||
|
.clip(RoundedCornerShape(IMAGE_RADIUS))
|
||||||
},
|
},
|
||||||
`else` = {
|
`else` = {
|
||||||
Modifier.size(IMAGE_SIZE)
|
Modifier
|
||||||
}
|
.size(IMAGE_SIZE)
|
||||||
)
|
|
||||||
.clip(RoundedCornerShape(IMAGE_RADIUS))
|
.clip(RoundedCornerShape(IMAGE_RADIUS))
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
}
|
||||||
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
when {
|
|
||||||
pendingFileUri != null -> {
|
|
||||||
Logger.d("AttachmentPreview", "Rendering: pendingFileUri, isUploading=$isUploading")
|
|
||||||
PendingImageContent(
|
|
||||||
uri = pendingFileUri,
|
|
||||||
isUploading = isUploading,
|
|
||||||
isImage = isImage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
file != null && isImage && dmEnvelope != null && !fileThumbnail.isNullOrBlank() -> {
|
|
||||||
Logger.d("AttachmentPreview", "Rendering: DecryptedImageContent file=${file.name} thumbLen=${fileThumbnail.length} aspectRatio=$fileAspectRatio")
|
|
||||||
DecryptedImageContent(
|
DecryptedImageContent(
|
||||||
file = file,
|
file = file!!,
|
||||||
envelope = dmEnvelope,
|
envelope = dmEnvelope!!,
|
||||||
currentUserId = currentUserId,
|
currentUserId = currentUserId,
|
||||||
thumbnailBase64 = fileThumbnail,
|
thumbnailBase64 = fileThumbnail!!,
|
||||||
aspectRatio = fileAspectRatio
|
aspectRatio = fileAspectRatio
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
file != null && !isImage -> {
|
|
||||||
Logger.d("AttachmentPreview", "Rendering: FileIconContent file=${file.name}")
|
|
||||||
FileIconContent(filename = file.name)
|
|
||||||
}
|
}
|
||||||
else -> {
|
isPendingImage -> {
|
||||||
Logger.d("AttachmentPreview", "Rendering: fallback Icon (file=$file, isImage=$isImage, hasEnvelope=${dmEnvelope != null}, thumbBlank=${fileThumbnail.isNullOrBlank()})")
|
Box(
|
||||||
Icon(
|
modifier = modifier
|
||||||
imageVector = Icons.Default.Image,
|
.conditional(
|
||||||
contentDescription = null,
|
fileAspectRatio != null && fileAspectRatio!! > 0f,
|
||||||
modifier = Modifier.size(48.dp),
|
`if` = {
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
Modifier
|
||||||
|
.aspectRatio(fileAspectRatio!!)
|
||||||
|
.sizeIn(maxWidth = IMAGE_SIZE, maxHeight = IMAGE_SIZE)
|
||||||
|
.clip(RoundedCornerShape(IMAGE_RADIUS))
|
||||||
|
},
|
||||||
|
`else` = {
|
||||||
|
Modifier
|
||||||
|
.size(IMAGE_SIZE)
|
||||||
|
.clip(RoundedCornerShape(IMAGE_RADIUS))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
PendingImageContent(
|
||||||
|
uri = pendingFileUri!!,
|
||||||
|
isUploading = isUploading,
|
||||||
|
isImage = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,23 +322,64 @@ private fun DecryptedImageContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatFileSize(bytes: Long): String {
|
||||||
|
return when {
|
||||||
|
bytes < 1024 -> "$bytes B"
|
||||||
|
bytes < 1024 * 1024 -> "${bytes / 1024} KB"
|
||||||
|
bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB"
|
||||||
|
else -> "${bytes / (1024 * 1024 * 1024)} GB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FileIconContent(filename: String) {
|
private fun FileIconContent(
|
||||||
Column(
|
filename: String,
|
||||||
modifier = Modifier.padding(16.dp),
|
sizeBytes: Long?,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
onClick: (() -> Unit)?,
|
||||||
|
isAuthor: Boolean,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val contentColor = if (isAuthor) Color.White else MaterialTheme.colorScheme.onSurface
|
||||||
|
val circleBackground = if (isAuthor) Color.White else MaterialTheme.colorScheme.primary
|
||||||
|
val iconTint = if (isAuthor) MaterialTheme.colorScheme.primary else Color.White
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.widthIn(max = 240.dp)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 4.dp)
|
||||||
|
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.background(circleBackground, RoundedCornerShape(20.dp)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.AttachFile,
|
imageVector = Icons.Default.Download,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(40.dp),
|
modifier = Modifier.size(22.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = iconTint
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(start = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = filename.take(20) + if (filename.length > 20) "…" else "",
|
text = filename.take(70) + if (filename.length > 70) "…" else "",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = contentColor,
|
||||||
maxLines = 2
|
maxLines = 2
|
||||||
)
|
)
|
||||||
|
if (sizeBytes != null) {
|
||||||
|
Text(
|
||||||
|
text = formatFileSize(sizeBytes),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = contentColor.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,20 @@ import ru.fromchat.api.Message
|
|||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
|
private fun isImageFilename(name: String): Boolean =
|
||||||
|
name.endsWith(".png", true) || name.endsWith(".jpg", true) ||
|
||||||
|
name.endsWith(".jpeg", true) || name.endsWith(".gif", true) || name.endsWith(".webp", true)
|
||||||
|
|
||||||
|
private fun isMessageCorrupted(message: Message): Boolean {
|
||||||
|
val files = message.files ?: return false
|
||||||
|
return files.withIndex().any { (index, file) ->
|
||||||
|
isImageFilename(file.name) && (
|
||||||
|
message.dmEnvelope == null ||
|
||||||
|
message.fileThumbnails?.getOrNull(index)?.isBlank() != false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageItem(
|
fun MessageItem(
|
||||||
@@ -200,7 +214,19 @@ fun MessageItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachments (images/files)
|
// Attachments (images/files) or corrupted message
|
||||||
|
if (isMessageCorrupted(message)) {
|
||||||
|
Text(
|
||||||
|
text = "_This message is corrupted and cannot be displayed.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (isAuthor) {
|
||||||
|
Color.White.copy(alpha = 0.8f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
if (message.pendingFileUri != null) {
|
if (message.pendingFileUri != null) {
|
||||||
AttachmentPreview(
|
AttachmentPreview(
|
||||||
file = null,
|
file = null,
|
||||||
@@ -208,6 +234,7 @@ fun MessageItem(
|
|||||||
currentUserId = null,
|
currentUserId = null,
|
||||||
pendingFileUri = message.pendingFileUri,
|
pendingFileUri = message.pendingFileUri,
|
||||||
isUploading = message.uploadProgress != null,
|
isUploading = message.uploadProgress != null,
|
||||||
|
isAuthor = isAuthor,
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -220,10 +247,13 @@ fun MessageItem(
|
|||||||
isUploading = false,
|
isUploading = false,
|
||||||
fileThumbnail = message.fileThumbnails?.getOrNull(index)?.takeIf { it.isNotBlank() },
|
fileThumbnail = message.fileThumbnails?.getOrNull(index)?.takeIf { it.isNotBlank() },
|
||||||
fileAspectRatio = message.fileAspectRatios?.getOrNull(index)?.takeIf { it > 0f },
|
fileAspectRatio = message.fileAspectRatios?.getOrNull(index)?.takeIf { it > 0f },
|
||||||
|
fileSizeBytes = message.fileSizes?.getOrNull(index),
|
||||||
|
isAuthor = isAuthor,
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (message.content.isNotBlank()) {
|
}
|
||||||
|
if (message.content.isNotBlank() && !isMessageCorrupted(message)) {
|
||||||
Text(
|
Text(
|
||||||
text = message.content,
|
text = message.content,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|||||||
@@ -176,13 +176,14 @@ class DmPanel(
|
|||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
val plaintext = runCatching { decryptEnvelope(envelope, currentUserId) }.getOrNull()
|
val plaintext = runCatching { decryptEnvelope(envelope, currentUserId) }.getOrNull()
|
||||||
if (plaintext != null) {
|
if (plaintext != null) {
|
||||||
val (content, fileThumbnails, fileAspectRatios) = parseDecryptedContent(plaintext)
|
val dec = parseDecryptedContent(plaintext)
|
||||||
updateMessage(envelope.id) {
|
updateMessage(envelope.id) {
|
||||||
it.copy(
|
it.copy(
|
||||||
content = content,
|
content = dec.text,
|
||||||
is_edited = true,
|
is_edited = true,
|
||||||
fileThumbnails = fileThumbnails ?: it.fileThumbnails,
|
fileThumbnails = dec.thumbnails ?: it.fileThumbnails,
|
||||||
fileAspectRatios = fileAspectRatios ?: it.fileAspectRatios
|
fileAspectRatios = dec.aspectRatios ?: it.fileAspectRatios,
|
||||||
|
fileSizes = dec.fileSizes ?: it.fileSizes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -191,11 +192,18 @@ class DmPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseDecryptedContent(plaintext: String): Triple<String, List<String>?, List<Float>?> {
|
private data class DecryptedContent(
|
||||||
|
val text: String,
|
||||||
|
val thumbnails: List<String>?,
|
||||||
|
val aspectRatios: List<Float>?,
|
||||||
|
val fileSizes: List<Long>?
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun parseDecryptedContent(plaintext: String): DecryptedContent {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val obj = json.parseToJsonElement(plaintext).jsonObject
|
val obj = json.parseToJsonElement(plaintext).jsonObject
|
||||||
val text = obj["text"]?.jsonPrimitive?.content ?: return@runCatching Triple(plaintext, null, null)
|
val text = obj["text"]?.jsonPrimitive?.content ?: return@runCatching DecryptedContent(plaintext, null, null, null)
|
||||||
val thumbArr = obj["fileThumbnails"]?.jsonArray ?: return@runCatching Triple(text, null, null)
|
val thumbArr = obj["fileThumbnails"]?.jsonArray ?: return@runCatching DecryptedContent(text, null, null, null)
|
||||||
val thumbnails = thumbArr.map { it.jsonPrimitive.content }
|
val thumbnails = thumbArr.map { it.jsonPrimitive.content }
|
||||||
val arArr = obj["fileAspectRatios"]?.jsonArray
|
val arArr = obj["fileAspectRatios"]?.jsonArray
|
||||||
val aspectRatios = arArr?.mapNotNull { elem ->
|
val aspectRatios = arArr?.mapNotNull { elem ->
|
||||||
@@ -206,16 +214,18 @@ class DmPanel(
|
|||||||
if (w != null && h != null && h > 0) w.toFloat() / h else null
|
if (w != null && h != null && h > 0) w.toFloat() / h else null
|
||||||
} else null
|
} else null
|
||||||
}?.takeIf { it.size == thumbnails.size }
|
}?.takeIf { it.size == thumbnails.size }
|
||||||
Logger.d("DmPanel", "parseDecryptedContent: thumbnails=${thumbnails.size} [${thumbnails.map { "len=${it.length}" }.joinToString()}], aspectRatios=${aspectRatios?.joinToString() ?: "null"}")
|
val sizesArr = obj["fileSizes"]?.jsonArray
|
||||||
Triple(text, thumbnails.ifEmpty { null }, aspectRatios)
|
val fileSizes = sizesArr?.mapNotNull { (it as? JsonPrimitive)?.content?.toLongOrNull() }?.takeIf { it.size == thumbnails.size }
|
||||||
|
Logger.d("DmPanel", "parseDecryptedContent: thumbnails=${thumbnails.size}, aspectRatios=${aspectRatios?.size}, fileSizes=${fileSizes?.size}")
|
||||||
|
DecryptedContent(text, thumbnails.ifEmpty { null }, aspectRatios, fileSizes)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
Logger.d("DmPanel", "parseDecryptedContent: parse failed, using plaintext fallback")
|
Logger.d("DmPanel", "parseDecryptedContent: parse failed, using plaintext fallback")
|
||||||
Triple(plaintext, null, null)
|
DecryptedContent(plaintext, null, null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMessage(envelope: DmEnvelope, plaintext: String): Message {
|
private fun createMessage(envelope: DmEnvelope, plaintext: String): Message {
|
||||||
val (content, fileThumbnails, fileAspectRatios) = parseDecryptedContent(plaintext)
|
val dec = parseDecryptedContent(plaintext)
|
||||||
val username = if (envelope.senderId == currentUserId) {
|
val username = if (envelope.senderId == currentUserId) {
|
||||||
"You"
|
"You"
|
||||||
} else {
|
} else {
|
||||||
@@ -224,7 +234,7 @@ class DmPanel(
|
|||||||
return Message(
|
return Message(
|
||||||
id = envelope.id,
|
id = envelope.id,
|
||||||
user_id = envelope.senderId,
|
user_id = envelope.senderId,
|
||||||
content = content,
|
content = dec.text,
|
||||||
timestamp = envelope.timestamp,
|
timestamp = envelope.timestamp,
|
||||||
is_read = envelope.recipientId == currentUserId,
|
is_read = envelope.recipientId == currentUserId,
|
||||||
is_edited = false,
|
is_edited = false,
|
||||||
@@ -236,8 +246,9 @@ class DmPanel(
|
|||||||
reactions = null,
|
reactions = null,
|
||||||
files = envelope.files,
|
files = envelope.files,
|
||||||
dmEnvelope = envelope,
|
dmEnvelope = envelope,
|
||||||
fileThumbnails = fileThumbnails,
|
fileThumbnails = dec.thumbnails,
|
||||||
fileAspectRatios = fileAspectRatios
|
fileAspectRatios = dec.aspectRatios,
|
||||||
|
fileSizes = dec.fileSizes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user