From efd21e6c0f781fe4d0ae1bedfe638cec373fa3c8 Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 5 Apr 2026 15:21:28 +0300 Subject: [PATCH] feat: animate chat action menu presentation feat: add subtle scale and fade transitions to the reaction and context action sheet --- .../messenger/ui/chat/ChatScreen.kt | 254 ++++++++++-------- 1 file changed, 136 insertions(+), 118 deletions(-) diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt index b0a7552..7a81384 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt @@ -82,6 +82,8 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -1342,6 +1344,10 @@ private fun ChatScreen( if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) { val selected = actionMenuMessage if (selected != null) { + var animateActionMenuContent by remember(selected.id) { mutableStateOf(false) } + LaunchedEffect(selected.id) { + animateActionMenuContent = true + } ModalBottomSheet( onDismissRequest = { actionMenuMessage = null @@ -1349,140 +1355,152 @@ private fun ChatScreen( }, sheetState = actionSheetState, ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + AnimatedVisibility( + visible = animateActionMenuContent, + enter = fadeIn(animationSpec = tween(150)) + scaleIn( + initialScale = 0.94f, + animationSpec = tween(150), + ), + exit = fadeOut(animationSpec = tween(100)) + scaleOut( + targetScale = 0.96f, + animationSpec = tween(100), + ), ) { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - listOf("❤️", "👍", "👎", "🔥", "🥰", "👏", "😁").forEach { emoji -> - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier - .clip(CircleShape) - .clickable { - onSelectMessage(selected) - onToggleReaction(emoji) - actionMenuMessage = null - } - .padding(horizontal = 12.dp, vertical = 8.dp), - ) { - Text(text = emoji) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + listOf("❤️", "👍", "👎", "🔥", "🥰", "👏", "😁").forEach { emoji -> + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier + .clip(CircleShape) + .clickable { + onSelectMessage(selected) + onToggleReaction(emoji) + actionMenuMessage = null + } + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Text(text = emoji) + } } } - } - Surface( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .clickable { - onReplySelected(selected) - actionMenuMessage = null - }, - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), - ) { - Row( + Surface( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Icon(imageVector = Icons.AutoMirrored.Filled.Reply, contentDescription = null) - Text(stringResource(id = R.string.chat_action_reply), style = MaterialTheme.typography.bodyLarge) - } - } - Surface( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .clickable(enabled = state.selectedCanEdit) { - onEditSelected(selected) - actionMenuMessage = null - }, - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Icon(imageVector = Icons.Filled.Edit, contentDescription = null) - Text( - stringResource(id = R.string.chat_action_edit), - style = MaterialTheme.typography.bodyLarge, - color = if (state.selectedCanEdit) { - MaterialTheme.colorScheme.onSurface - } else { - MaterialTheme.colorScheme.onSurfaceVariant + .clip(RoundedCornerShape(12.dp)) + .clickable { + onReplySelected(selected) + actionMenuMessage = null }, - ) + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon(imageVector = Icons.AutoMirrored.Filled.Reply, contentDescription = null) + Text(stringResource(id = R.string.chat_action_reply), style = MaterialTheme.typography.bodyLarge) + } } - } - Surface( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .clickable { - onSelectMessage(selected) - onForwardSelected() - actionMenuMessage = null - }, - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), - ) { - Row( + Surface( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), + .clip(RoundedCornerShape(12.dp)) + .clickable(enabled = state.selectedCanEdit) { + onEditSelected(selected) + actionMenuMessage = null + }, + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), ) { - Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = null) - Text(stringResource(id = R.string.chat_action_forward), style = MaterialTheme.typography.bodyLarge) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon(imageVector = Icons.Filled.Edit, contentDescription = null) + Text( + stringResource(id = R.string.chat_action_edit), + style = MaterialTheme.typography.bodyLarge, + color = if (state.selectedCanEdit) { + MaterialTheme.colorScheme.onSurface + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ) + } } - } - Surface( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .clickable { - onSelectMessage(selected) - pendingDeleteForAll = false - showDeleteDialog = true - actionMenuMessage = null - }, - color = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.45f), - ) { - Row( + Surface( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), + .clip(RoundedCornerShape(12.dp)) + .clickable { + onSelectMessage(selected) + onForwardSelected() + actionMenuMessage = null + }, + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), ) { - Icon( - imageVector = Icons.Filled.DeleteOutline, - contentDescription = null, - tint = MaterialTheme.colorScheme.error, - ) - Text( - stringResource(id = R.string.chat_action_delete), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.error, - ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = null) + Text(stringResource(id = R.string.chat_action_forward), style = MaterialTheme.typography.bodyLarge) + } } + Surface( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .clickable { + onSelectMessage(selected) + pendingDeleteForAll = false + showDeleteDialog = true + actionMenuMessage = null + }, + color = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.45f), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon( + imageVector = Icons.Filled.DeleteOutline, + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + ) + Text( + stringResource(id = R.string.chat_action_delete), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.error, + ) + } + } + TextButton( + onClick = { + actionMenuMessage = null + onClearSelection() + }, + modifier = Modifier.fillMaxWidth(), + ) { Text(stringResource(id = R.string.common_close)) } } - TextButton( - onClick = { - actionMenuMessage = null - onClearSelection() - }, - modifier = Modifier.fillMaxWidth(), - ) { Text(stringResource(id = R.string.common_close)) } } } }