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.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.composables.app.AnimatedAppIconComposable
|
||||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
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.data.room.download.Download
|
||||||
import com.aurora.store.util.CommonUtil.getETAString
|
import com.aurora.store.util.CommonUtil.getETAString
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,16 +82,20 @@ fun DownloadComposable(
|
|||||||
val speed = "${Formatter.formatShortFileSize(LocalContext.current, download.speed)}/s"
|
val speed = "${Formatter.formatShortFileSize(LocalContext.current, download.speed)}/s"
|
||||||
val eta = getETAString(LocalContext.current, download.timeRemaining)
|
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(
|
fun requestClear() {
|
||||||
modifier = Modifier
|
coroutineScope.launch {
|
||||||
.clickable(onClick = onClick)
|
isVisible = false
|
||||||
.padding(
|
delay(300) // Let the animation play
|
||||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
onClear()
|
||||||
vertical = dimensionResource(R.dimen.padding_small)
|
}
|
||||||
)
|
}
|
||||||
) {
|
|
||||||
|
@Composable
|
||||||
|
fun SetupDownload() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
@@ -100,7 +108,8 @@ fun DownloadComposable(
|
|||||||
progress = download.progress.toFloat()
|
progress = download.progress.toFloat()
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
modifier = Modifier
|
||||||
|
.padding(horizontal = dimensionResource(R.dimen.margin_small)),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = download.displayName,
|
text = download.displayName,
|
||||||
@@ -112,10 +121,10 @@ fun DownloadComposable(
|
|||||||
text = stringResource(download.downloadStatus.localized),
|
text = stringResource(download.downloadStatus.localized),
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
AnimatedContent(targetState = download.isRunning) { isRunning ->
|
AnimatedContent(targetState = download.downloadStatus) { status ->
|
||||||
Text(
|
Text(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
text = if (isRunning) {
|
text = if (status in DownloadStatus.running) {
|
||||||
"$progress • $speed • $eta"
|
"$progress • $speed • $eta"
|
||||||
} else {
|
} else {
|
||||||
DateUtils.getRelativeTimeSpanString(
|
DateUtils.getRelativeTimeSpanString(
|
||||||
@@ -142,7 +151,7 @@ fun DownloadComposable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
SplitButtonDefaults.LeadingButton(onClick = onClear) {
|
SplitButtonDefaults.LeadingButton(onClick = { requestClear() }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_delete_forever),
|
painter = painterResource(R.drawable.ic_delete_forever),
|
||||||
contentDescription = stringResource(R.string.action_clear)
|
contentDescription = stringResource(R.string.action_clear)
|
||||||
@@ -168,7 +177,10 @@ fun DownloadComposable(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SetupExpandedMenu() {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isExpanded,
|
visible = isExpanded,
|
||||||
enter = slideInVertically() + expandVertically() + fadeIn(),
|
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)
|
@Preview(showBackground = true)
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
package com.aurora.store.compose.composables
|
package com.aurora.store.compose.composables
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -20,6 +23,11 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
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.AppPreviewProvider
|
||||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||||
import com.aurora.store.data.room.favourite.Favourite
|
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
|
* Composable to display a favourite app in a list
|
||||||
@@ -55,60 +65,76 @@ fun FavouriteComposable(
|
|||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onClear: () -> Unit = {}
|
onClear: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Row(
|
val coroutineScope = rememberCoroutineScope()
|
||||||
modifier = modifier
|
var isVisible by remember { mutableStateOf(true) }
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(onClick = onClick)
|
fun requestClear() {
|
||||||
.padding(
|
coroutineScope.launch {
|
||||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
isVisible = false
|
||||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
delay(300) // Let the animation play
|
||||||
),
|
onClear()
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
}
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isVisible,
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.weight(1F)) {
|
Row(
|
||||||
AsyncImage(
|
modifier = modifier
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
.fillMaxWidth()
|
||||||
.data(favourite.iconURL)
|
.clickable(onClick = onClick)
|
||||||
.crossfade(true)
|
.padding(
|
||||||
.build(),
|
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||||
contentDescription = null,
|
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||||
contentScale = ContentScale.Crop,
|
),
|
||||||
modifier = Modifier
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
) {
|
||||||
)
|
Row(modifier = Modifier.weight(1F)) {
|
||||||
Column(
|
AsyncImage(
|
||||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
) {
|
.data(favourite.iconURL)
|
||||||
Text(
|
.crossfade(true)
|
||||||
text = favourite.displayName,
|
.build(),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
contentDescription = null,
|
||||||
maxLines = 1,
|
contentScale = ContentScale.Crop,
|
||||||
overflow = TextOverflow.Ellipsis
|
modifier = Modifier
|
||||||
|
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||||
|
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||||
)
|
)
|
||||||
Text(
|
Column(
|
||||||
text = favourite.packageName,
|
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
||||||
style = MaterialTheme.typography.bodySmall,
|
) {
|
||||||
maxLines = 1,
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis
|
text = favourite.displayName,
|
||||||
)
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
Text(
|
maxLines = 1,
|
||||||
text = DateUtils.formatDateTime(
|
overflow = TextOverflow.Ellipsis
|
||||||
LocalContext.current,
|
)
|
||||||
favourite.added,
|
Text(
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
text = favourite.packageName,
|
||||||
),
|
style = MaterialTheme.typography.bodySmall,
|
||||||
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 ->
|
) { index ->
|
||||||
downloads[index]?.let { download ->
|
downloads[index]?.let { download ->
|
||||||
DownloadComposable(
|
DownloadComposable(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
download = download,
|
download = download,
|
||||||
onClick = { onNavigateToAppDetails(download.packageName) },
|
onClick = { onNavigateToAppDetails(download.packageName) },
|
||||||
onClear = { onClear(download) },
|
onClear = { onClear(download) },
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
@@ -96,6 +100,13 @@ private fun ScreenContent(
|
|||||||
onImportFavourites: () -> Unit = {},
|
onImportFavourites: () -> Unit = {},
|
||||||
onExportFavourites: () -> 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
|
@Composable
|
||||||
fun SetupMenu() {
|
fun SetupMenu() {
|
||||||
@@ -122,18 +133,14 @@ private fun ScreenContent(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||||
) {
|
) {
|
||||||
when (favourites.loadState.refresh) {
|
when {
|
||||||
is LoadState.Loading -> ProgressComposable()
|
favourites.loadState.refresh is LoadState.Loading && initialLoad -> {
|
||||||
|
ProgressComposable()
|
||||||
is LoadState.Error -> {
|
|
||||||
ErrorComposable(
|
|
||||||
modifier = Modifier.padding(paddingValues),
|
|
||||||
icon = painterResource(R.drawable.ic_disclaimer),
|
|
||||||
message = stringResource(R.string.error)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
initialLoad = false
|
||||||
|
|
||||||
if (favourites.itemCount == 0) {
|
if (favourites.itemCount == 0) {
|
||||||
ErrorComposable(
|
ErrorComposable(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
@@ -148,6 +155,7 @@ private fun ScreenContent(
|
|||||||
) { index ->
|
) { index ->
|
||||||
favourites[index]?.let { favourite ->
|
favourites[index]?.let { favourite ->
|
||||||
FavouriteComposable(
|
FavouriteComposable(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
favourite = favourite,
|
favourite = favourite,
|
||||||
onClick = { onNavigateToAppDetails(favourite.packageName) },
|
onClick = { onNavigateToAppDetails(favourite.packageName) },
|
||||||
onClear = { onRemoveFavourite(favourite.packageName) }
|
onClear = { onRemoveFavourite(favourite.packageName) }
|
||||||
|
|||||||
Reference in New Issue
Block a user