feat: animate chat action menu presentation
feat: add subtle scale and fade transitions to the reaction and context action sheet
This commit is contained in:
@@ -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)) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user