Исправлены аватар в сайдбаре и медиа-пузыри в группах

This commit is contained in:
2026-03-19 15:28:44 +05:00
parent 75d0f4726b
commit 9a411ac473
3 changed files with 96 additions and 56 deletions

View File

@@ -126,6 +126,25 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private val groupMembersCountCache = java.util.concurrent.ConcurrentHashMap<String, Int>()
private const val GROUP_MESSAGE_STACK_TIME_DIFF_MS = 5 * 60_000L
private const val DIRECT_MESSAGE_STACK_TIME_DIFF_MS = 60_000L
private fun isSameLocalDay(firstTimestampMs: Long, secondTimestampMs: Long): Boolean {
val firstCalendar =
java.util.Calendar.getInstance().apply {
timeInMillis = firstTimestampMs
}
val secondCalendar =
java.util.Calendar.getInstance().apply {
timeInMillis = secondTimestampMs
}
return firstCalendar.get(java.util.Calendar.ERA) ==
secondCalendar.get(java.util.Calendar.ERA) &&
firstCalendar.get(java.util.Calendar.YEAR) ==
secondCalendar.get(java.util.Calendar.YEAR) &&
firstCalendar.get(java.util.Calendar.DAY_OF_YEAR) ==
secondCalendar.get(java.util.Calendar.DAY_OF_YEAR)
}
private data class IncomingRunAvatarAccumulator(
val senderPublicKey: String,
@@ -777,16 +796,26 @@ fun ChatDetailScreen(
}
val isMessageBoundary: (ChatMessage, ChatMessage?) -> Boolean =
remember(isGroupChat, currentUserPublicKey, user.publicKey) {
val maxStackTimeDiffMs =
if (isGroupChat) {
GROUP_MESSAGE_STACK_TIME_DIFF_MS
} else {
DIRECT_MESSAGE_STACK_TIME_DIFF_MS
}
{ currentMessage, adjacentMessage ->
if (adjacentMessage == null) {
true
} else if (adjacentMessage.isOutgoing != currentMessage.isOutgoing) {
true
} else if (
kotlin.math.abs(
currentMessage.timestamp.time -
adjacentMessage.timestamp.time
) > 60_000L
!isSameLocalDay(
currentMessage.timestamp.time,
adjacentMessage.timestamp.time
) ||
kotlin.math.abs(
currentMessage.timestamp.time -
adjacentMessage.timestamp.time
) > maxStackTimeDiffMs
) {
true
} else if (
@@ -2531,41 +2560,12 @@ fun ChatDetailScreen(
isMessageBoundary(message, prevMessage)
val isGroupStart =
isMessageBoundary(message, nextMessage)
val runHeadIndex =
messageRunNewestIndex.getOrNull(
index
) ?: index
val runTailIndex =
messageRunOldestIndexByHead
.getOrNull(
runHeadIndex
)
?: runHeadIndex
val isHeadPhase =
incomingRunAvatarUiState
.showOnRunHeads
.contains(
runHeadIndex
)
val isTailPhase =
incomingRunAvatarUiState
.showOnRunTails
.contains(
runHeadIndex
)
val showIncomingGroupAvatar =
isGroupChat &&
!message.isOutgoing &&
senderPublicKeyForMessage
.isNotBlank() &&
((index ==
runHeadIndex &&
isHeadPhase &&
showTail) ||
(index ==
runTailIndex &&
isTailPhase &&
isGroupStart))
isGroupStart
Column {
if (showDate

View File

@@ -834,10 +834,6 @@ fun ChatsListScreen(
modifier =
Modifier.size(72.dp)
.clip(CircleShape)
.background(
Color.White
.copy(alpha = 0.2f)
)
.combinedClickable(
onClick = {
scope.launch {
@@ -858,8 +854,7 @@ fun ChatsListScreen(
accountPublicKey
}
}
)
.padding(3.dp),
),
contentAlignment =
Alignment.Center
) {
@@ -868,7 +863,7 @@ fun ChatsListScreen(
accountPublicKey,
avatarRepository =
avatarRepository,
size = 66.dp,
size = 72.dp,
isDarkTheme =
isDarkTheme,
displayName =

View File

@@ -759,14 +759,9 @@ fun MessageBubble(
} else if (isStandaloneGroupInvite) {
Modifier.widthIn(min = 180.dp, max = 260.dp)
} else if (hasImageWithCaption || hasOnlyMedia) {
if (hasGroupSenderName) {
Modifier.widthIn(min = photoWidth)
} else {
Modifier.width(
photoWidth
) // 🔥 Фиксированная ширина = размер фото (убирает лишний
// отступ)
}
Modifier.width(
photoWidth
) // Для медиа держим фиксированную ширину как в desktop/telegram
} else {
Modifier.widthIn(min = if (hasGroupSenderName) 120.dp else 60.dp, max = 280.dp)
}
@@ -849,8 +844,19 @@ fun MessageBubble(
!message.isOutgoing &&
senderName.isNotBlank()
) {
val isMediaGroupBubble = hasImageWithCaption || hasOnlyMedia
val senderLabelTopPadding =
if (isMediaGroupBubble) 8.dp else 0.dp
val senderLabelHorizontalPadding =
if (isMediaGroupBubble) 10.dp else 0.dp
Row(
modifier = Modifier.padding(bottom = 4.dp),
modifier =
Modifier.padding(
start = senderLabelHorizontalPadding,
end = senderLabelHorizontalPadding,
top = senderLabelTopPadding,
bottom = 4.dp
),
verticalAlignment = Alignment.CenterVertically
) {
Text(
@@ -2433,6 +2439,7 @@ private fun ForwardedImagePreview(
onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit
) {
val context = androidx.compose.ui.platform.LocalContext.current
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
val cacheKey = "img_${attachment.id}"
var imageBitmap by remember(attachment.id) {
@@ -2527,17 +2534,55 @@ private fun ForwardedImagePreview(
}
}
val imgWidth = attachment.width.takeIf { it > 0 } ?: 300
val imgHeight = attachment.height.takeIf { it > 0 } ?: 200
val aspectRatio = (imgWidth.toFloat() / imgHeight.toFloat()).coerceIn(0.5f, 2.5f)
val maxPhotoWidthDp = TelegramBubbleSpec.maxPhotoWidth(configuration.screenWidthDp).dp
val minPhotoWidthDp = TelegramBubbleSpec.minPhotoWidth.dp
val minPhotoHeightDp = TelegramBubbleSpec.minPhotoHeight.dp
val maxPhotoHeightDp = TelegramBubbleSpec.maxPhotoHeight.dp
val (previewWidth, previewHeight) =
remember(
attachment.width,
attachment.height,
configuration.screenWidthDp
) {
val actualWidth = attachment.width.takeIf { it > 0 } ?: 0
val actualHeight = attachment.height.takeIf { it > 0 } ?: 0
if (actualWidth > 0 && actualHeight > 0) {
val ar = actualWidth.toFloat() / actualHeight.toFloat()
var w = if (ar >= 1f) {
maxPhotoWidthDp.value
} else {
maxPhotoWidthDp.value * 0.75f
}
var h = w / ar
if (h > maxPhotoHeightDp.value) {
h = maxPhotoHeightDp.value
w = h * ar
}
if (h < minPhotoHeightDp.value) {
h = minPhotoHeightDp.value
w = h * ar
}
w = w.coerceIn(minPhotoWidthDp.value, maxPhotoWidthDp.value)
h = h.coerceIn(minPhotoHeightDp.value, maxPhotoHeightDp.value)
w.dp to h.dp
} else {
val fallbackWidth = maxPhotoWidthDp
val fallbackHeight = (maxPhotoWidthDp.value * 0.75f)
.coerceIn(minPhotoHeightDp.value, maxPhotoHeightDp.value)
.dp
fallbackWidth to fallbackHeight
}
}
var photoBoxBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(aspectRatio)
.heightIn(max = 200.dp)
.width(previewWidth)
.height(previewHeight)
.clip(RoundedCornerShape(6.dp))
.background(Color.Gray.copy(alpha = 0.2f))
.onGloballyPositioned { coords ->