feat: add expand, queue chip, and account switcher animations

- F4: AnimatedContent with expand/shrink transitions on "Show more" text
- F6: Pulsing alpha animation on queue chip when status is UPLOADING
- F10: Staggered slideInHorizontally + fadeIn entrance for account switcher items
This commit is contained in:
Paweł Orzech 2026-03-19 14:21:12 +01:00
parent 5ab2cbafdc
commit 0713bd912e
No known key found for this signature in database

View file

@ -7,6 +7,7 @@ import android.content.Intent
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.rememberInfiniteTransition
@ -1124,7 +1125,19 @@ fun AccountSwitcherBottomSheet(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp) modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)
) )
accounts.forEach { account -> accounts.forEachIndexed { index, account ->
var itemVisible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(SwooshMotion.StaggerDelayMs * index)
itemVisible = true
}
AnimatedVisibility(
visible = itemVisible,
enter = slideInHorizontally(
initialOffsetX = { -it / 4 },
animationSpec = SwooshMotion.gentle()
) + fadeIn(SwooshMotion.quick())
) {
AccountListItem( AccountListItem(
account = account, account = account,
isActive = account.id == activeAccountId, isActive = account.id == activeAccountId,
@ -1133,6 +1146,7 @@ fun AccountSwitcherBottomSheet(
onRename = { onRenameAccount(account) } onRename = { onRenameAccount(account) }
) )
} }
}
if (accounts.size < 5) { if (accounts.size < 5) {
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp))
@ -1475,25 +1489,32 @@ fun PostCardContent(
maxLines = if (expanded) Int.MAX_VALUE else 8 maxLines = if (expanded) Int.MAX_VALUE else 8
) )
} else { } else {
AnimatedContent(
targetState = expanded,
transitionSpec = {
(fadeIn(SwooshMotion.quick()) + expandVertically(animationSpec = SwooshMotion.snappy()))
.togetherWith(fadeOut(SwooshMotion.quick()) + shrinkVertically(animationSpec = SwooshMotion.snappy()))
},
label = "expandText"
) { isExpanded ->
Column {
Text( Text(
text = displayText, text = if (isExpanded || post.textContent.length <= 280) post.textContent
style = MaterialTheme.typography.bodyLarge, else post.textContent.take(280) + "...",
color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium,
maxLines = if (expanded) Int.MAX_VALUE else 8, maxLines = if (isExpanded) Int.MAX_VALUE else 8,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
if (!isExpanded && post.textContent.length > 280) {
TextButton(
onClick = { expanded = true },
contentPadding = PaddingValues(0.dp)
) {
Text("Show more", style = MaterialTheme.typography.labelMedium)
}
}
}
} }
if (!expanded && post.textContent.length > 280) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Show more",
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.clickable { expanded = true }
.padding(vertical = 2.dp)
)
} }
// Image grid // Image grid
@ -1595,10 +1616,22 @@ fun PostCardContent(
QueueStatus.FAILED -> "Upload failed" QueueStatus.FAILED -> "Upload failed"
else -> "" else -> ""
} }
val isUploading = post.queueStatus == QueueStatus.UPLOADING
val infiniteTransition = rememberInfiniteTransition(label = "queuePulse")
val chipAlpha by infiniteTransition.animateFloat(
initialValue = if (isUploading) 0.6f else 1f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(600),
repeatMode = RepeatMode.Reverse
),
label = "uploadPulse"
)
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
AssistChip( AssistChip(
onClick = {}, onClick = {},
label = { Text(queueLabel, style = MaterialTheme.typography.labelSmall) } label = { Text(queueLabel, style = MaterialTheme.typography.labelSmall) },
modifier = Modifier.graphicsLayer { alpha = if (isUploading) chipAlpha else 1f }
) )
if (post.queueStatus == QueueStatus.QUEUED_PUBLISH || post.queueStatus == QueueStatus.QUEUED_SCHEDULED) { if (post.queueStatus == QueueStatus.QUEUED_PUBLISH || post.queueStatus == QueueStatus.QUEUED_SCHEDULED) {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))