Fix flickering

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-02-19 22:06:39 +03:00
Unverified
parent 323ec99c12
commit 900fc3429d
4 changed files with 500 additions and 375 deletions
@@ -1,8 +1,6 @@
package ru.fromchat.ui.chat package ru.fromchat.ui.chat
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -49,6 +47,8 @@ import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.withSaveLayer import androidx.compose.ui.graphics.withSaveLayer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
@@ -79,9 +79,8 @@ fun AttachmentPreview(
fileIndex: Int? = null, fileIndex: Int? = null,
onFileClick: (() -> Unit)? = null, onFileClick: (() -> Unit)? = null,
onImageClick: (() -> Unit)? = null, onImageClick: (() -> Unit)? = null,
sharedImageKey: Any? = null, onImageBounds: ((Rect) -> Unit)? = null,
sharedTransitionScope: SharedTransitionScope? = null, isExpanded: Boolean = false,
animatedVisibilityScope: AnimatedVisibilityScope? = null,
isAuthor: Boolean = false, isAuthor: Boolean = false,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -123,18 +122,10 @@ fun AttachmentPreview(
} }
isImageWithThumb -> { isImageWithThumb -> {
var isFullyLoaded by remember { mutableStateOf(false) } var isFullyLoaded by remember { mutableStateOf(false) }
val sharedModifier = if (sharedImageKey != null && sharedTransitionScope != null && animatedVisibilityScope != null) {
with(sharedTransitionScope) {
Modifier.sharedElement(
rememberSharedContentState(key = sharedImageKey),
animatedVisibilityScope = animatedVisibilityScope
)
}
} else Modifier
Box( Box(
modifier = modifier modifier = modifier
.then( .then(
if (onImageClick != null && isFullyLoaded) Modifier.clickable(onClick = onImageClick) if (onImageClick != null && isFullyLoaded && !isExpanded) Modifier.clickable(onClick = onImageClick)
else Modifier else Modifier
) )
.conditional( .conditional(
@@ -148,10 +139,28 @@ fun AttachmentPreview(
Modifier.size(IMAGE_SIZE) Modifier.size(IMAGE_SIZE)
} }
) )
.then(sharedModifier) .clip(RoundedCornerShape(IMAGE_RADIUS))
.clip(RoundedCornerShape(IMAGE_RADIUS)), .then(
if (onImageBounds != null) {
Modifier.onGloballyPositioned { coords ->
val pos = coords.positionInRoot()
val size = coords.size
onImageBounds(
Rect(
pos.x,
pos.y,
pos.x + size.width.toFloat(),
pos.y + size.height.toFloat()
)
)
}
} else {
Modifier
}
),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (!isExpanded) {
DecryptedImageContent( DecryptedImageContent(
messageId = messageId ?: -1, messageId = messageId ?: -1,
fileIndex = fileIndex ?: 0, fileIndex = fileIndex ?: 0,
@@ -160,11 +169,11 @@ fun AttachmentPreview(
currentUserId = currentUserId, currentUserId = currentUserId,
thumbnailBase64 = fileThumbnail, thumbnailBase64 = fileThumbnail,
aspectRatio = fileAspectRatio, aspectRatio = fileAspectRatio,
sharedImageKey = sharedImageKey,
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
onFullyLoaded = { isFullyLoaded = it } onFullyLoaded = { isFullyLoaded = it }
) )
} else {
Box(modifier = Modifier.fillMaxSize())
}
} }
} }
isPendingImage -> { isPendingImage -> {
@@ -264,9 +273,6 @@ private fun DecryptedImageContent(
currentUserId: Int?, currentUserId: Int?,
thumbnailBase64: String, thumbnailBase64: String,
aspectRatio: Float?, aspectRatio: Float?,
sharedImageKey: Any? = null,
sharedTransitionScope: SharedTransitionScope? = null,
animatedVisibilityScope: AnimatedVisibilityScope? = null,
onFullyLoaded: (Boolean) -> Unit = {} onFullyLoaded: (Boolean) -> Unit = {}
) { ) {
var cachedPath by remember(messageId, fileIndex, file.path) { var cachedPath by remember(messageId, fileIndex, file.path) {
@@ -3,7 +3,6 @@ package ru.fromchat.ui.chat
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -43,6 +42,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -184,10 +184,9 @@ fun ChatScreen(
) )
} }
var expandedImage by remember { mutableStateOf<Pair<Message, Int>?>(null) } var expandedImage by remember { mutableStateOf<Pair<Message, Int>?>(null) }
var isImageClosing by remember { mutableStateOf(false) }
BackHandler(enabled = expandedImage != null) { val imageThumbBounds = remember { mutableStateMapOf<String, Rect>() }
expandedImage = null val expandedImageKey = expandedImage?.let { (msg, idx) -> "img_${msg.id}_$idx" }
}
// Collect WebSocket messages // Collect WebSocket messages
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -301,15 +300,8 @@ fun ChatScreen(
} }
} }
AnimatedContent(
targetState = expandedImage, Box(modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize(),
transitionSpec = {
fadeIn(animationSpec = tween(300)) togetherWith ExitTransition.None
},
label = "image_fullscreen"
) { expanded ->
if (expanded == null) {
Scaffold( Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
@@ -449,7 +441,7 @@ fun ChatScreen(
) )
}, },
bottomBar = { bottomBar = {
Column( // New Column to hold ChatInput below the LazyColumn Column(
modifier = Modifier modifier = Modifier
.windowInsetsPadding(WindowInsets.ime) .windowInsetsPadding(WindowInsets.ime)
.fillMaxWidth() .fillMaxWidth()
@@ -539,7 +531,6 @@ fun ChatScreen(
.fillMaxSize() .fillMaxSize()
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures { detectTapGestures {
// Close context menu on outside tap
if (contextMenuState.isOpen) { if (contextMenuState.isOpen) {
contextMenuState = contextMenuState.copy(isOpen = false) contextMenuState = contextMenuState.copy(isOpen = false)
} }
@@ -581,7 +572,6 @@ fun ChatScreen(
MessageItem( MessageItem(
message = message, message = message,
isAuthor = message.user_id == currentUserId, isAuthor = message.user_id == currentUserId,
currentUserId = currentUserId,
onLongPress = { onLongPress = {
contextMenuState = ContextMenuState( contextMenuState = ContextMenuState(
isOpen = true, isOpen = true,
@@ -596,9 +586,13 @@ fun ChatScreen(
tapOffset = offset tapOffset = offset
}, },
onImageClick = { msg, idx -> expandedImage = msg to idx }, onImageClick = { msg, idx -> expandedImage = msg to idx },
sharedTransitionScope = sharedTransitionScope, onImageBounds = { key, rect ->
animatedVisibilityScope = this@AnimatedContent, imageThumbBounds[key] = rect
showUsername = panel.showUsernamesInMessages },
expandedImageKey = expandedImageKey,
isImageClosing = isImageClosing,
showUsername = panel.showUsernamesInMessages,
currentUserId = currentUserId
) )
} }
} }
@@ -632,19 +626,25 @@ fun ChatScreen(
) )
} }
} }
} else {
expanded.let { (msg, idx) -> expandedImage?.let { (msg, idx) ->
val key = "img_${msg.id}_$idx"
ImageFullscreenPreview( ImageFullscreenPreview(
message = msg, message = msg,
fileIndex = idx, fileIndex = idx,
currentUserId = currentUserId, currentUserId = currentUserId,
onDismiss = { expandedImage = null }, onDismiss = {
isImageClosing = false
expandedImage = null
},
onClosingChange = { isImageClosing = it },
onReply = { m -> onReply = { m ->
replyTo = m replyTo = m
if (editingMessage != null) { if (editingMessage != null) {
editingMessage = null editingMessage = null
inputText = "" inputText = ""
} }
isImageClosing = false
expandedImage = null expandedImage = null
}, },
onDelete = { m -> onDelete = { m ->
@@ -653,12 +653,12 @@ fun ChatScreen(
} }
}, },
onSave = { _, _ -> /* TODO: platform-specific save to gallery */ }, onSave = { _, _ -> /* TODO: platform-specific save to gallery */ },
sharedTransitionScope = sharedTransitionScope, sharedTransitionScope = null,
animatedVisibilityScope = this@AnimatedContent, animatedVisibilityScope = null,
sharedImageKey = "img_${msg.id}_$idx", sharedImageKey = null,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
thumbnailBounds = imageThumbBounds[key]
) )
} }
} }
}
} }
@@ -47,6 +47,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@@ -56,10 +57,12 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import ru.fromchat.api.Message import ru.fromchat.api.Message
import ru.fromchat.ui.BackHandler
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@@ -67,6 +70,14 @@ import kotlin.time.Instant
private val MENU_BG_ALPHA = 0.5f private val MENU_BG_ALPHA = 0.5f
private data class InitialTransform(
val scale: Float,
val offsetX: Float,
val offsetY: Float,
val cornerRadius: Float,
val bgAlpha: Float
)
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
private fun formatDateTime(timestamp: String): String { private fun formatDateTime(timestamp: String): String {
return try { return try {
@@ -92,10 +103,12 @@ fun ImageFullscreenPreview(
onReply: (Message) -> Unit, onReply: (Message) -> Unit,
onDelete: (Message) -> Unit, onDelete: (Message) -> Unit,
onSave: (Message, Int) -> Unit, onSave: (Message, Int) -> Unit,
onClosingChange: (Boolean) -> Unit = {},
sharedTransitionScope: SharedTransitionScope?, sharedTransitionScope: SharedTransitionScope?,
animatedVisibilityScope: AnimatedVisibilityScope?, animatedVisibilityScope: AnimatedVisibilityScope?,
sharedImageKey: Any? = null, sharedImageKey: Any? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
thumbnailBounds: Rect? = null
) { ) {
val file = message.files?.getOrNull(fileIndex) ?: return val file = message.files?.getOrNull(fileIndex) ?: return
val envelope = message.dmEnvelope val envelope = message.dmEnvelope
@@ -116,11 +129,20 @@ fun ImageFullscreenPreview(
var menusVisible by remember { mutableStateOf(true) } var menusVisible by remember { mutableStateOf(true) }
var dismissRequested by remember { mutableStateOf(false) } var dismissRequested by remember { mutableStateOf(false) }
val backgroundAlpha = remember { Animatable(1f) }
var hasPlayedOpenAnimation by remember(thumbnailBounds) { mutableStateOf(thumbnailBounds == null) }
val isInitialOpenState = thumbnailBounds != null && !hasPlayedOpenAnimation
val effectiveBackgroundAlpha = if (isInitialOpenState) 0f else backgroundAlpha.value
val effectiveMenusVisible = if (isInitialOpenState) false else menusVisible
BackHandler(enabled = true) {
dismissRequested = true
}
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.background(Color.Black) .background(Color.Black.copy(alpha = effectiveBackgroundAlpha))
) { ) {
BoxWithConstraints( BoxWithConstraints(
modifier = Modifier modifier = Modifier
@@ -145,16 +167,71 @@ fun ImageFullscreenPreview(
) )
} }
else -> { else -> {
var scale by remember { mutableStateOf(1f) } val initial = remember(
var offset by remember { mutableStateOf(Offset.Zero) } thumbnailBounds, containerWidth, containerHeight, contentHeightAtScale1
val scaleAnim = remember { Animatable(1f) } ) {
val offsetXAnim = remember { Animatable(0f) } if (thumbnailBounds != null && fileAspectRatio != null) {
val offsetYAnim = remember { Animatable(0f) } val fullTop = (containerHeight - contentHeightAtScale1) / 2f
val fullCenter = Offset(
x = containerWidth / 2f,
y = fullTop + contentHeightAtScale1 / 2f
)
val thumbCenter = thumbnailBounds.center
val thumbWidth = thumbnailBounds.width
val s = thumbWidth / containerWidth
val o = thumbCenter - fullCenter
InitialTransform(s, o.x, o.y, 8f, 0f)
} else {
InitialTransform(1f, 0f, 0f, 0f, 1f)
}
}
var scale by remember { mutableStateOf(initial.scale) }
var offset by remember { mutableStateOf(Offset(initial.offsetX, initial.offsetY)) }
val scaleAnim = remember { Animatable(initial.scale) }
val offsetXAnim = remember { Animatable(initial.offsetX) }
val offsetYAnim = remember { Animatable(initial.offsetY) }
val cornerRadiusAnim = remember { Animatable(initial.cornerRadius) }
val state = rememberTransformableState { zoomChange, offsetChange, _ -> val state = rememberTransformableState { zoomChange, offsetChange, _ ->
scale = (scale * zoomChange).coerceIn(1f, 12f) scale = (scale * zoomChange).coerceIn(1f, 12f)
offset += offsetChange offset += offsetChange
} }
LaunchedEffect(thumbnailBounds) {
if (thumbnailBounds != null && !hasPlayedOpenAnimation) {
hasPlayedOpenAnimation = true
val fullTop = (containerHeight - contentHeightAtScale1) / 2f
val fullCenter = Offset(
x = containerWidth / 2f,
y = fullTop + contentHeightAtScale1 / 2f
)
val thumbCenter = thumbnailBounds.center
val thumbWidth = thumbnailBounds.width
val startScale = thumbWidth / containerWidth
val startOffset = thumbCenter - fullCenter
scale = startScale
offset = startOffset
scaleAnim.snapTo(startScale)
offsetXAnim.snapTo(startOffset.x)
offsetYAnim.snapTo(startOffset.y)
cornerRadiusAnim.snapTo(8f)
backgroundAlpha.snapTo(0f)
menusVisible = false
coroutineScope {
launch { scaleAnim.animateTo(1f, tween(250)) }
launch { offsetXAnim.animateTo(0f, tween(250)) }
launch { offsetYAnim.animateTo(0f, tween(250)) }
launch { cornerRadiusAnim.animateTo(0f, tween(250)) }
launch { backgroundAlpha.animateTo(1f, tween(250)) }
}
menusVisible = true
}
}
LaunchedEffect(dismissRequested) { LaunchedEffect(dismissRequested) {
if (!dismissRequested) return@LaunchedEffect if (!dismissRequested) return@LaunchedEffect
@@ -162,14 +239,50 @@ fun ImageFullscreenPreview(
offsetXAnim.value != 0f || offsetXAnim.value != 0f ||
offsetYAnim.value != 0f offsetYAnim.value != 0f
if (thumbnailBounds != null) {
menusVisible = false
val fullTop = (containerHeight - contentHeightAtScale1) / 2f
val fullCenter = Offset(
x = containerWidth / 2f,
y = fullTop + contentHeightAtScale1 / 2f
)
val thumbCenter = thumbnailBounds.center
val thumbWidth = thumbnailBounds.width
val targetScale = thumbWidth / containerWidth
val targetOffset = thumbCenter - fullCenter
scaleAnim.snapTo(scale)
offsetXAnim.snapTo(offset.x)
offsetYAnim.snapTo(offset.y)
cornerRadiusAnim.snapTo(0f)
coroutineScope {
launch { scaleAnim.animateTo(targetScale, tween(250)) }
launch { offsetXAnim.animateTo(targetOffset.x, tween(250)) }
launch { offsetYAnim.animateTo(targetOffset.y, tween(250)) }
launch { cornerRadiusAnim.animateTo(8f, tween(250)) }
launch { backgroundAlpha.animateTo(0f, tween(250)) }
}
} else {
if (hasTransform) { if (hasTransform) {
menusVisible = false
coroutineScope { coroutineScope {
launch { scaleAnim.animateTo(1f, tween(220)) } launch { scaleAnim.animateTo(1f, tween(220)) }
launch { offsetXAnim.animateTo(0f, tween(220)) } launch { offsetXAnim.animateTo(0f, tween(220)) }
launch { offsetYAnim.animateTo(0f, tween(220)) } launch { offsetYAnim.animateTo(0f, tween(220)) }
launch { cornerRadiusAnim.animateTo(0f, tween(220)) }
launch { backgroundAlpha.animateTo(0f, tween(220)) }
}
} else {
backgroundAlpha.animateTo(0f, tween(220))
} }
} }
onClosingChange(true)
delay(50)
onClosingChange(false)
onDismiss() onDismiss()
} }
LaunchedEffect(scale, offset, state.isTransformInProgress) { LaunchedEffect(scale, offset, state.isTransformInProgress) {
@@ -238,6 +351,8 @@ fun ImageFullscreenPreview(
.graphicsLayer { .graphicsLayer {
scaleX = scaleAnim.value scaleX = scaleAnim.value
scaleY = scaleAnim.value scaleY = scaleAnim.value
shape = androidx.compose.foundation.shape.RoundedCornerShape(cornerRadiusAnim.value.dp)
clip = cornerRadiusAnim.value > 0f
}, },
contentScale = ContentScale.FillWidth contentScale = ContentScale.FillWidth
) )
@@ -249,7 +364,7 @@ fun ImageFullscreenPreview(
// Top bar: back, display name + date/time, 3-dot menu // Top bar: back, display name + date/time, 3-dot menu
AnimatedVisibility( AnimatedVisibility(
visible = menusVisible, visible = effectiveMenusVisible,
enter = androidx.compose.animation.fadeIn(), enter = androidx.compose.animation.fadeIn(),
exit = androidx.compose.animation.fadeOut(), exit = androidx.compose.animation.fadeOut(),
modifier = Modifier modifier = Modifier
@@ -349,7 +464,7 @@ fun ImageFullscreenPreview(
// Bottom: message text // Bottom: message text
AnimatedVisibility( AnimatedVisibility(
visible = menusVisible && message.content.isNotBlank(), visible = effectiveMenusVisible && message.content.isNotBlank(),
enter = androidx.compose.animation.fadeIn(), enter = androidx.compose.animation.fadeIn(),
exit = androidx.compose.animation.fadeOut(), exit = androidx.compose.animation.fadeOut(),
modifier = Modifier modifier = Modifier
@@ -2,7 +2,6 @@ package ru.fromchat.ui.chat
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -34,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -64,11 +64,12 @@ fun MessageItem(
onLongPress: () -> Unit, onLongPress: () -> Unit,
onTapPosition: (Offset) -> Unit = {}, onTapPosition: (Offset) -> Unit = {},
onImageClick: ((Message, Int) -> Unit)? = null, onImageClick: ((Message, Int) -> Unit)? = null,
sharedTransitionScope: SharedTransitionScope? = null, onImageBounds: ((String, Rect) -> Unit)? = null,
animatedVisibilityScope: AnimatedVisibilityScope? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showUsername: Boolean = true, showUsername: Boolean = true,
currentUserId: Int? = null currentUserId: Int? = null,
expandedImageKey: String? = null,
isImageClosing: Boolean = false
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = true, visible = true,
@@ -241,6 +242,7 @@ fun MessageItem(
} }
message.files?.forEachIndexed { index, file -> message.files?.forEachIndexed { index, file ->
val isImage = isImageFilename(file.name) val isImage = isImageFilename(file.name)
val imageKey = if (isImage) "img_${message.id}_$index" else null
AttachmentPreview( AttachmentPreview(
file = file, file = file,
dmEnvelope = message.dmEnvelope, dmEnvelope = message.dmEnvelope,
@@ -252,11 +254,13 @@ fun MessageItem(
fileSizeBytes = message.fileSizes?.getOrNull(index), fileSizeBytes = message.fileSizes?.getOrNull(index),
messageId = if (isImage) message.id else null, messageId = if (isImage) message.id else null,
fileIndex = if (isImage) index else null, fileIndex = if (isImage) index else null,
isAuthor = isAuthor, onFileClick = null,
onImageClick = if (isImage) { { onImageClick?.invoke(message, index) } } else null, onImageClick = if (isImage) { { onImageClick?.invoke(message, index) } } else null,
sharedImageKey = if (isImage && sharedTransitionScope != null && animatedVisibilityScope != null) "img_${message.id}_$index" else null, onImageBounds = if (isImage && imageKey != null && onImageBounds != null) {
sharedTransitionScope = sharedTransitionScope, { rect -> onImageBounds.invoke(imageKey, rect) }
animatedVisibilityScope = animatedVisibilityScope, } else null,
isExpanded = isImage && expandedImageKey != null && expandedImageKey == imageKey && !isImageClosing,
isAuthor = isAuthor,
modifier = Modifier.padding( modifier = Modifier.padding(
horizontal = if (isImage) 2.dp else 12.dp, horizontal = if (isImage) 2.dp else 12.dp,
vertical = if (isImage) 2.dp else 4.dp vertical = if (isImage) 2.dp else 4.dp