Исправлены аватар в сайдбаре и медиа-пузыри в группах
This commit is contained in:
@@ -126,6 +126,25 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
private val groupMembersCountCache = java.util.concurrent.ConcurrentHashMap<String, Int>()
|
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(
|
private data class IncomingRunAvatarAccumulator(
|
||||||
val senderPublicKey: String,
|
val senderPublicKey: String,
|
||||||
@@ -777,16 +796,26 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
val isMessageBoundary: (ChatMessage, ChatMessage?) -> Boolean =
|
val isMessageBoundary: (ChatMessage, ChatMessage?) -> Boolean =
|
||||||
remember(isGroupChat, currentUserPublicKey, user.publicKey) {
|
remember(isGroupChat, currentUserPublicKey, user.publicKey) {
|
||||||
|
val maxStackTimeDiffMs =
|
||||||
|
if (isGroupChat) {
|
||||||
|
GROUP_MESSAGE_STACK_TIME_DIFF_MS
|
||||||
|
} else {
|
||||||
|
DIRECT_MESSAGE_STACK_TIME_DIFF_MS
|
||||||
|
}
|
||||||
{ currentMessage, adjacentMessage ->
|
{ currentMessage, adjacentMessage ->
|
||||||
if (adjacentMessage == null) {
|
if (adjacentMessage == null) {
|
||||||
true
|
true
|
||||||
} else if (adjacentMessage.isOutgoing != currentMessage.isOutgoing) {
|
} else if (adjacentMessage.isOutgoing != currentMessage.isOutgoing) {
|
||||||
true
|
true
|
||||||
} else if (
|
} else if (
|
||||||
kotlin.math.abs(
|
!isSameLocalDay(
|
||||||
currentMessage.timestamp.time -
|
currentMessage.timestamp.time,
|
||||||
adjacentMessage.timestamp.time
|
adjacentMessage.timestamp.time
|
||||||
) > 60_000L
|
) ||
|
||||||
|
kotlin.math.abs(
|
||||||
|
currentMessage.timestamp.time -
|
||||||
|
adjacentMessage.timestamp.time
|
||||||
|
) > maxStackTimeDiffMs
|
||||||
) {
|
) {
|
||||||
true
|
true
|
||||||
} else if (
|
} else if (
|
||||||
@@ -2531,41 +2560,12 @@ fun ChatDetailScreen(
|
|||||||
isMessageBoundary(message, prevMessage)
|
isMessageBoundary(message, prevMessage)
|
||||||
val isGroupStart =
|
val isGroupStart =
|
||||||
isMessageBoundary(message, nextMessage)
|
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 =
|
val showIncomingGroupAvatar =
|
||||||
isGroupChat &&
|
isGroupChat &&
|
||||||
!message.isOutgoing &&
|
!message.isOutgoing &&
|
||||||
senderPublicKeyForMessage
|
senderPublicKeyForMessage
|
||||||
.isNotBlank() &&
|
.isNotBlank() &&
|
||||||
((index ==
|
isGroupStart
|
||||||
runHeadIndex &&
|
|
||||||
isHeadPhase &&
|
|
||||||
showTail) ||
|
|
||||||
(index ==
|
|
||||||
runTailIndex &&
|
|
||||||
isTailPhase &&
|
|
||||||
isGroupStart))
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
if (showDate
|
if (showDate
|
||||||
|
|||||||
@@ -834,10 +834,6 @@ fun ChatsListScreen(
|
|||||||
modifier =
|
modifier =
|
||||||
Modifier.size(72.dp)
|
Modifier.size(72.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(
|
|
||||||
Color.White
|
|
||||||
.copy(alpha = 0.2f)
|
|
||||||
)
|
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -858,8 +854,7 @@ fun ChatsListScreen(
|
|||||||
accountPublicKey
|
accountPublicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
.padding(3.dp),
|
|
||||||
contentAlignment =
|
contentAlignment =
|
||||||
Alignment.Center
|
Alignment.Center
|
||||||
) {
|
) {
|
||||||
@@ -868,7 +863,7 @@ fun ChatsListScreen(
|
|||||||
accountPublicKey,
|
accountPublicKey,
|
||||||
avatarRepository =
|
avatarRepository =
|
||||||
avatarRepository,
|
avatarRepository,
|
||||||
size = 66.dp,
|
size = 72.dp,
|
||||||
isDarkTheme =
|
isDarkTheme =
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
displayName =
|
displayName =
|
||||||
|
|||||||
@@ -759,14 +759,9 @@ fun MessageBubble(
|
|||||||
} else if (isStandaloneGroupInvite) {
|
} else if (isStandaloneGroupInvite) {
|
||||||
Modifier.widthIn(min = 180.dp, max = 260.dp)
|
Modifier.widthIn(min = 180.dp, max = 260.dp)
|
||||||
} else if (hasImageWithCaption || hasOnlyMedia) {
|
} else if (hasImageWithCaption || hasOnlyMedia) {
|
||||||
if (hasGroupSenderName) {
|
Modifier.width(
|
||||||
Modifier.widthIn(min = photoWidth)
|
photoWidth
|
||||||
} else {
|
) // Для медиа держим фиксированную ширину как в desktop/telegram
|
||||||
Modifier.width(
|
|
||||||
photoWidth
|
|
||||||
) // 🔥 Фиксированная ширина = размер фото (убирает лишний
|
|
||||||
// отступ)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Modifier.widthIn(min = if (hasGroupSenderName) 120.dp else 60.dp, max = 280.dp)
|
Modifier.widthIn(min = if (hasGroupSenderName) 120.dp else 60.dp, max = 280.dp)
|
||||||
}
|
}
|
||||||
@@ -849,8 +844,19 @@ fun MessageBubble(
|
|||||||
!message.isOutgoing &&
|
!message.isOutgoing &&
|
||||||
senderName.isNotBlank()
|
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(
|
Row(
|
||||||
modifier = Modifier.padding(bottom = 4.dp),
|
modifier =
|
||||||
|
Modifier.padding(
|
||||||
|
start = senderLabelHorizontalPadding,
|
||||||
|
end = senderLabelHorizontalPadding,
|
||||||
|
top = senderLabelTopPadding,
|
||||||
|
bottom = 4.dp
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -2433,6 +2439,7 @@ private fun ForwardedImagePreview(
|
|||||||
onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit
|
onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = androidx.compose.ui.platform.LocalContext.current
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
|
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
|
||||||
val cacheKey = "img_${attachment.id}"
|
val cacheKey = "img_${attachment.id}"
|
||||||
|
|
||||||
var imageBitmap by remember(attachment.id) {
|
var imageBitmap by remember(attachment.id) {
|
||||||
@@ -2527,17 +2534,55 @@ private fun ForwardedImagePreview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val imgWidth = attachment.width.takeIf { it > 0 } ?: 300
|
val maxPhotoWidthDp = TelegramBubbleSpec.maxPhotoWidth(configuration.screenWidthDp).dp
|
||||||
val imgHeight = attachment.height.takeIf { it > 0 } ?: 200
|
val minPhotoWidthDp = TelegramBubbleSpec.minPhotoWidth.dp
|
||||||
val aspectRatio = (imgWidth.toFloat() / imgHeight.toFloat()).coerceIn(0.5f, 2.5f)
|
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) }
|
var photoBoxBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.width(previewWidth)
|
||||||
.aspectRatio(aspectRatio)
|
.height(previewHeight)
|
||||||
.heightIn(max = 200.dp)
|
|
||||||
.clip(RoundedCornerShape(6.dp))
|
.clip(RoundedCornerShape(6.dp))
|
||||||
.background(Color.Gray.copy(alpha = 0.2f))
|
.background(Color.Gray.copy(alpha = 0.2f))
|
||||||
.onGloballyPositioned { coords ->
|
.onGloballyPositioned { coords ->
|
||||||
|
|||||||
Reference in New Issue
Block a user