compose: Play short animation on removing items
Compose makes it easy to animate items. Play a short animation when items are removed from downloads and favourites. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -36,7 +36,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -53,8 +54,11 @@ import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.app.AnimatedAppIconComposable
|
||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.CommonUtil.getETAString
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
@@ -78,16 +82,20 @@ fun DownloadComposable(
|
||||
val speed = "${Formatter.formatShortFileSize(LocalContext.current, download.speed)}/s"
|
||||
val eta = getETAString(LocalContext.current, download.timeRemaining)
|
||||
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var isVisible by remember { mutableStateOf(true) }
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
)
|
||||
) {
|
||||
fun requestClear() {
|
||||
coroutineScope.launch {
|
||||
isVisible = false
|
||||
delay(300) // Let the animation play
|
||||
onClear()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetupDownload() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
@@ -100,7 +108,8 @@ fun DownloadComposable(
|
||||
progress = download.progress.toFloat()
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||
) {
|
||||
Text(
|
||||
text = download.displayName,
|
||||
@@ -112,10 +121,10 @@ fun DownloadComposable(
|
||||
text = stringResource(download.downloadStatus.localized),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
AnimatedContent(targetState = download.isRunning) { isRunning ->
|
||||
AnimatedContent(targetState = download.downloadStatus) { status ->
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = if (isRunning) {
|
||||
text = if (status in DownloadStatus.running) {
|
||||
"$progress • $speed • $eta"
|
||||
} else {
|
||||
DateUtils.getRelativeTimeSpanString(
|
||||
@@ -142,7 +151,7 @@ fun DownloadComposable(
|
||||
}
|
||||
|
||||
else -> {
|
||||
SplitButtonDefaults.LeadingButton(onClick = onClear) {
|
||||
SplitButtonDefaults.LeadingButton(onClick = { requestClear() }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete_forever),
|
||||
contentDescription = stringResource(R.string.action_clear)
|
||||
@@ -168,7 +177,10 @@ fun DownloadComposable(
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetupExpandedMenu() {
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
enter = slideInVertically() + expandVertically() + fadeIn(),
|
||||
@@ -209,6 +221,23 @@ fun DownloadComposable(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clickable(onClick = onClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
)
|
||||
) {
|
||||
SetupDownload()
|
||||
SetupExpandedMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -20,6 +23,11 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -40,6 +48,8 @@ import com.aurora.store.R
|
||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Composable to display a favourite app in a list
|
||||
@@ -55,60 +65,76 @@ fun FavouriteComposable(
|
||||
onClick: () -> Unit = {},
|
||||
onClear: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var isVisible by remember { mutableStateOf(true) }
|
||||
|
||||
fun requestClear() {
|
||||
coroutineScope.launch {
|
||||
isVisible = false
|
||||
delay(300) // Let the animation play
|
||||
onClear()
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1F)) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(favourite.iconURL)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
Text(
|
||||
text = favourite.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1F)) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(favourite.iconURL)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
)
|
||||
Text(
|
||||
text = favourite.packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
favourite.added,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
Text(
|
||||
text = favourite.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = favourite.packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
favourite.added,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { requestClear() }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_favorite_checked),
|
||||
contentDescription = stringResource(R.string.action_favourite)
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onClear) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_favorite_checked),
|
||||
contentDescription = stringResource(R.string.action_favourite)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ private fun ScreenContent(
|
||||
) { index ->
|
||||
downloads[index]?.let { download ->
|
||||
DownloadComposable(
|
||||
modifier = Modifier.animateItem(),
|
||||
download = download,
|
||||
onClick = { onNavigateToAppDetails(download.packageName) },
|
||||
onClear = { onClear(download) },
|
||||
|
||||
@@ -13,6 +13,10 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
@@ -96,6 +100,13 @@ private fun ScreenContent(
|
||||
onImportFavourites: () -> Unit = {},
|
||||
onExportFavourites: () -> Unit = {}
|
||||
) {
|
||||
/*
|
||||
* For some reason paging3 frequently out-of-nowhere invalidates the list which causes
|
||||
* the loading animation to play again even if the keys are same causing a glitching effect.
|
||||
*
|
||||
* Save the initial loading state to make sure we don't replay the loading animation again.
|
||||
*/
|
||||
var initialLoad by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
@Composable
|
||||
fun SetupMenu() {
|
||||
@@ -122,18 +133,14 @@ private fun ScreenContent(
|
||||
.fillMaxSize()
|
||||
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||
) {
|
||||
when (favourites.loadState.refresh) {
|
||||
is LoadState.Loading -> ProgressComposable()
|
||||
|
||||
is LoadState.Error -> {
|
||||
ErrorComposable(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
icon = painterResource(R.drawable.ic_disclaimer),
|
||||
message = stringResource(R.string.error)
|
||||
)
|
||||
when {
|
||||
favourites.loadState.refresh is LoadState.Loading && initialLoad -> {
|
||||
ProgressComposable()
|
||||
}
|
||||
|
||||
else -> {
|
||||
initialLoad = false
|
||||
|
||||
if (favourites.itemCount == 0) {
|
||||
ErrorComposable(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
@@ -148,6 +155,7 @@ private fun ScreenContent(
|
||||
) { index ->
|
||||
favourites[index]?.let { favourite ->
|
||||
FavouriteComposable(
|
||||
modifier = Modifier.animateItem(),
|
||||
favourite = favourite,
|
||||
onClick = { onNavigateToAppDetails(favourite.packageName) },
|
||||
onClear = { onRemoveFavourite(favourite.packageName) }
|
||||
|
||||
Reference in New Issue
Block a user