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.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut 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.LinearEasing
import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
@@ -1342,6 +1344,10 @@ private fun ChatScreen(
if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) { if (actionMenuMessage != null && state.actionState.mode != MessageSelectionMode.MULTI) {
val selected = actionMenuMessage val selected = actionMenuMessage
if (selected != null) { if (selected != null) {
var animateActionMenuContent by remember(selected.id) { mutableStateOf(false) }
LaunchedEffect(selected.id) {
animateActionMenuContent = true
}
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onDismissRequest = {
actionMenuMessage = null actionMenuMessage = null
@@ -1349,140 +1355,152 @@ private fun ChatScreen(
}, },
sheetState = actionSheetState, sheetState = actionSheetState,
) { ) {
Column( AnimatedVisibility(
modifier = Modifier visible = animateActionMenuContent,
.fillMaxWidth() enter = fadeIn(animationSpec = tween(150)) + scaleIn(
.padding(horizontal = 16.dp, vertical = 8.dp), initialScale = 0.94f,
verticalArrangement = Arrangement.spacedBy(8.dp), animationSpec = tween(150),
),
exit = fadeOut(animationSpec = tween(100)) + scaleOut(
targetScale = 0.96f,
animationSpec = tween(100),
),
) { ) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { Column(
listOf("❤️", "👍", "👎", "🔥", "🥰", "👏", "😁").forEach { emoji -> modifier = Modifier
Surface( .fillMaxWidth()
shape = CircleShape, .padding(horizontal = 16.dp, vertical = 8.dp),
color = MaterialTheme.colorScheme.surfaceVariant, verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier ) {
.clip(CircleShape) Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
.clickable { listOf("❤️", "👍", "👎", "🔥", "🥰", "👏", "😁").forEach { emoji ->
onSelectMessage(selected) Surface(
onToggleReaction(emoji) shape = CircleShape,
actionMenuMessage = null color = MaterialTheme.colorScheme.surfaceVariant,
} modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp), .clip(CircleShape)
) { .clickable {
Text(text = emoji) onSelectMessage(selected)
onToggleReaction(emoji)
actionMenuMessage = null
}
.padding(horizontal = 12.dp, vertical = 8.dp),
) {
Text(text = emoji)
}
} }
} }
} Surface(
Surface(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.clickable {
onReplySelected(selected)
actionMenuMessage = null
},
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f),
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 10.dp), .clip(RoundedCornerShape(12.dp))
verticalAlignment = Alignment.CenterVertically, .clickable {
horizontalArrangement = Arrangement.spacedBy(12.dp), onReplySelected(selected)
) { actionMenuMessage = null
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
}, },
) 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(
Surface(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.clickable {
onSelectMessage(selected)
onForwardSelected()
actionMenuMessage = null
},
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f),
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 10.dp), .clip(RoundedCornerShape(12.dp))
verticalAlignment = Alignment.CenterVertically, .clickable(enabled = state.selectedCanEdit) {
horizontalArrangement = Arrangement.spacedBy(12.dp), onEditSelected(selected)
actionMenuMessage = null
},
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f),
) { ) {
Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = null) Row(
Text(stringResource(id = R.string.chat_action_forward), style = MaterialTheme.typography.bodyLarge) 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(
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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 10.dp), .clip(RoundedCornerShape(12.dp))
verticalAlignment = Alignment.CenterVertically, .clickable {
horizontalArrangement = Arrangement.spacedBy(12.dp), onSelectMessage(selected)
onForwardSelected()
actionMenuMessage = null
},
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f),
) { ) {
Icon( Row(
imageVector = Icons.Filled.DeleteOutline, modifier = Modifier
contentDescription = null, .fillMaxWidth()
tint = MaterialTheme.colorScheme.error, .padding(horizontal = 12.dp, vertical = 10.dp),
) verticalAlignment = Alignment.CenterVertically,
Text( horizontalArrangement = Arrangement.spacedBy(12.dp),
stringResource(id = R.string.chat_action_delete), ) {
style = MaterialTheme.typography.bodyLarge, Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = null)
color = MaterialTheme.colorScheme.error, 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)) }
} }
} }
} }