|
|
|
|
@ -158,62 +158,62 @@ fun MessageBubble(message: Message, playbackState: ChatViewModel.PlaybackState =
|
|
|
|
|
horizontalArrangement = alignment |
|
|
|
|
) { |
|
|
|
|
MessageWithFooter(footer) { |
|
|
|
|
Column( |
|
|
|
|
modifier = Modifier |
|
|
|
|
.widthIn(min = minBubbleWidth, max = if (isVoice) voiceMaxWidth else 300.dp) |
|
|
|
|
// Тень рисуем до clip, чтобы выступающие справа и снизу края оставались видны. |
|
|
|
|
.drawBehind { |
|
|
|
|
val offset = 2.dp.toPx() |
|
|
|
|
val radius = 12.dp.toPx() |
|
|
|
|
drawRoundRect(ChatColors.BubbleShadow, topLeft = Offset(offset, offset), size = size, |
|
|
|
|
cornerRadius = CornerRadius(radius, radius)) |
|
|
|
|
} |
|
|
|
|
.clip(RoundedCornerShape(12.dp)) |
|
|
|
|
.background(bgColor) |
|
|
|
|
.padding(12.dp) |
|
|
|
|
) { |
|
|
|
|
if (!isVoice) Text(if (message.isOutgoing) stringResource(R.string.you) else message.author, |
|
|
|
|
fontSize = 13.sp, fontWeight = FontWeight.Bold, color = ChatColors.Secondary) |
|
|
|
|
Spacer(Modifier.height(2.dp)) |
|
|
|
|
Column( |
|
|
|
|
modifier = Modifier |
|
|
|
|
.widthIn(min = minBubbleWidth, max = if (isVoice) voiceMaxWidth else 300.dp) |
|
|
|
|
// Тень рисуем до clip, чтобы выступающие справа и снизу края оставались видны. |
|
|
|
|
.drawBehind { |
|
|
|
|
val offset = 2.dp.toPx() |
|
|
|
|
val radius = 12.dp.toPx() |
|
|
|
|
drawRoundRect(ChatColors.BubbleShadow, topLeft = Offset(offset, offset), size = size, |
|
|
|
|
cornerRadius = CornerRadius(radius, radius)) |
|
|
|
|
} |
|
|
|
|
.clip(RoundedCornerShape(12.dp)) |
|
|
|
|
.background(bgColor) |
|
|
|
|
.padding(12.dp) |
|
|
|
|
) { |
|
|
|
|
if (!isVoice) Text(if (message.isOutgoing) stringResource(R.string.you) else message.author, |
|
|
|
|
fontSize = 13.sp, fontWeight = FontWeight.Bold, color = ChatColors.Secondary) |
|
|
|
|
Spacer(Modifier.height(2.dp)) |
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
message.inviteGroupName != null -> { |
|
|
|
|
val clipboard = LocalClipboardManager.current |
|
|
|
|
LaunchedEffect(message.id, message.inviteGroupName) { |
|
|
|
|
LogManager.addLog("DEBUG", "PM", "invitation rendered: seq=${message.id} " + |
|
|
|
|
"group_name_bytes=${message.inviteGroupName.toByteArray().size} link_bytes=${message.text.length}") |
|
|
|
|
when { |
|
|
|
|
message.inviteGroupName != null -> { |
|
|
|
|
val clipboard = LocalClipboardManager.current |
|
|
|
|
LaunchedEffect(message.id, message.inviteGroupName) { |
|
|
|
|
LogManager.addLog("DEBUG", "PM", "invitation rendered: seq=${message.id} " + |
|
|
|
|
"group_name_bytes=${message.inviteGroupName.toByteArray().size} link_bytes=${message.text.length}") |
|
|
|
|
} |
|
|
|
|
Text(stringResource(R.string.group_invitation, message.inviteGroupName), fontSize = 17.sp, color = textColor) |
|
|
|
|
Text(message.text, fontSize = 17.sp, color = Color(0xFF3390EC), textDecoration = TextDecoration.Underline, |
|
|
|
|
maxLines = 1, overflow = TextOverflow.Ellipsis, |
|
|
|
|
modifier = Modifier.fillMaxWidth().combinedClickable( |
|
|
|
|
onClick = { onLink(message.text) }, |
|
|
|
|
onLongClickLabel = stringResource(R.string.copy_link), |
|
|
|
|
onLongClick = { |
|
|
|
|
clipboard.setText(AnnotatedString(message.text)) |
|
|
|
|
LogManager.addLog("DEBUG", "PM", "invitation link copied: seq=${message.id} bytes=${message.text.length}") |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
isVoice -> VoiceBubble(message, textColor, isActive, playbackState, onPlayVoice, onSeekVoice, onDownloadFile) |
|
|
|
|
isVideo -> VideoBubble(message, textColor, onDownloadFile, onPlayVideo) |
|
|
|
|
isImage -> ImageBubble(message, textColor, onDownloadFile, onOpenImage) |
|
|
|
|
isMedia -> FileBubble(message, textColor, onDownloadFile, onOpenFile) |
|
|
|
|
else -> { |
|
|
|
|
EmojiText(message.text, fontSize = 17.sp, color = textColor, onLink = onLink) |
|
|
|
|
} |
|
|
|
|
Text(stringResource(R.string.group_invitation, message.inviteGroupName), fontSize = 17.sp, color = textColor) |
|
|
|
|
Text(message.text, fontSize = 17.sp, color = Color(0xFF3390EC), textDecoration = TextDecoration.Underline, |
|
|
|
|
maxLines = 1, overflow = TextOverflow.Ellipsis, |
|
|
|
|
modifier = Modifier.fillMaxWidth().combinedClickable( |
|
|
|
|
onClick = { onLink(message.text) }, |
|
|
|
|
onLongClickLabel = stringResource(R.string.copy_link), |
|
|
|
|
onLongClick = { |
|
|
|
|
clipboard.setText(AnnotatedString(message.text)) |
|
|
|
|
LogManager.addLog("DEBUG", "PM", "invitation link copied: seq=${message.id} bytes=${message.text.length}") |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
isVoice -> VoiceBubble(message, textColor, isActive, playbackState, onPlayVoice, onSeekVoice, onDownloadFile) |
|
|
|
|
isVideo -> VideoBubble(message, textColor, onDownloadFile, onPlayVideo) |
|
|
|
|
isImage -> ImageBubble(message, textColor, onDownloadFile, onOpenImage) |
|
|
|
|
isMedia -> FileBubble(message, textColor, onDownloadFile, onOpenFile) |
|
|
|
|
else -> { |
|
|
|
|
EmojiText(message.text, fontSize = 17.sp, color = textColor, onLink = onLink) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!isVoice) { |
|
|
|
|
Spacer(Modifier.height(2.dp)) |
|
|
|
|
Text( |
|
|
|
|
listOf(formatTime(message.ts), deliveryLabel(message)).filter { it.isNotEmpty() }.joinToString(" · "), |
|
|
|
|
fontSize = 11.sp, |
|
|
|
|
color = ChatColors.Secondary, |
|
|
|
|
modifier = Modifier.align(Alignment.End) |
|
|
|
|
) |
|
|
|
|
if (!isVoice) { |
|
|
|
|
Spacer(Modifier.height(2.dp)) |
|
|
|
|
Text( |
|
|
|
|
listOf(formatTime(message.ts), deliveryLabel(message)).filter { it.isNotEmpty() }.joinToString(" · "), |
|
|
|
|
fontSize = 11.sp, |
|
|
|
|
color = ChatColors.Secondary, |
|
|
|
|
modifier = Modifier.align(Alignment.End) |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@Composable |
|
|
|
|
|