feat: animate chat action menu presentation
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

feat: add subtle scale and fade transitions to the reaction and context action sheet
This commit is contained in:
2026-04-05 15:21:28 +03:00
parent 1484d67d7f
commit efd21e6c0f

View File

@@ -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)) }
}
}
}