Loading app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultListItem.kt +42 −41 Original line number Diff line number Diff line Loading @@ -19,13 +19,14 @@ package foundation.e.apps.ui.compose.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement Loading @@ -33,6 +34,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding Loading @@ -42,8 +44,6 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Star import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface Loading Loading @@ -277,19 +277,18 @@ private fun PrimaryActionArea( val accentColor = MaterialTheme.colorScheme.tertiary val outlineColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.25f) val animationDuration = 250 val hasProgress = uiState.progressFraction > 0f val targetContainerColor = when { hasProgress -> accentColor.copy(alpha = 0.12f) uiState.isFilledStyle && uiState.enabled -> accentColor uiState.isFilledStyle -> accentColor.copy(alpha = 0.12f) else -> Color.Transparent } val targetBorderColor = when { uiState.isFilledStyle -> Color.Transparent else -> outlineColor } val targetTextColor = when { hasProgress && uiState.progressFraction >= 0.5f -> MaterialTheme.colorScheme.onPrimary hasProgress -> accentColor uiState.isFilledStyle -> MaterialTheme.colorScheme.onPrimary else -> accentColor } Loading @@ -300,41 +299,51 @@ private fun PrimaryActionArea( label = "containerColor" ) val animatedBorderColor = animateColorAsState( targetValue = targetBorderColor, animationSpec = tween(durationMillis = animationDuration), label = "borderColor" ) val animatedTextColor = animateColorAsState( targetValue = targetTextColor, animationSpec = tween(durationMillis = animationDuration), label = "textColor" ) val animatedProgress = animateFloatAsState( targetValue = uiState.progressFraction.coerceIn(0f, 1f), animationSpec = tween(durationMillis = animationDuration), label = "progressFraction" ) Column(horizontalAlignment = Alignment.End) { Button( onClick = onPrimaryClick, enabled = uiState.enabled, val buttonShape = RoundedCornerShape(4.dp) val showBorder = !hasProgress && !uiState.isFilledStyle val borderColor = outlineColor Box( modifier = Modifier .widthIn(min = 88.dp) .height(40.dp), shape = RoundedCornerShape(4.dp), colors = ButtonDefaults.buttonColors( containerColor = animatedContainerColor.value, contentColor = animatedTextColor.value, disabledContainerColor = animatedContainerColor.value, disabledContentColor = animatedTextColor.value.copy(alpha = 0.38f), ), border = BorderStroke(1.dp, animatedBorderColor.value), contentPadding = ButtonDefaults.ContentPadding, .then( if (hasProgress) Modifier.width(88.dp) else Modifier.widthIn(min = 88.dp) ) .height(40.dp) .clip(buttonShape) .background(animatedContainerColor.value) .then( if (showBorder) Modifier.border(1.dp, borderColor, buttonShape) else Modifier ) .clickable(enabled = uiState.enabled, onClick = onPrimaryClick), contentAlignment = Alignment.Center, ) { if (hasProgress) { Box( modifier = Modifier .fillMaxHeight() .fillMaxWidth(animatedProgress.value.coerceIn(0f, 1f)) .align(Alignment.CenterStart) .background(accentColor) ) } val showSpinner = uiState.isInProgress && uiState.label.isBlank() val isProgressLabel = uiState.label.endsWith("%") // Stable keys here val animationKey = when { showSpinner -> "spinner" isProgressLabel -> "progress" else -> uiState.label } AnimatedContent( Loading @@ -354,16 +363,6 @@ private fun PrimaryActionArea( ) } "progress" -> { Text( text = uiState.label, maxLines = 1, overflow = TextOverflow.Clip, color = animatedTextColor.value, style = MaterialTheme.typography.labelLarge, ) } else -> { Text( text = targetKey, Loading @@ -371,6 +370,7 @@ private fun PrimaryActionArea( overflow = TextOverflow.Clip, color = animatedTextColor.value, style = MaterialTheme.typography.labelLarge, modifier = Modifier.padding(horizontal = 16.dp), ) } } Loading Loading @@ -423,6 +423,7 @@ data class PrimaryActionUiState( val isFilledStyle: Boolean, val showMore: Boolean = false, val actionIntent: InstallButtonAction = InstallButtonAction.NoOp, val progressFraction: Float = 0f, ) // --- Previews --- Loading app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +1 −1 Original line number Diff line number Diff line Loading @@ -625,7 +625,6 @@ private fun Application.toSearchResultUiState(buttonState: InstallButtonState): isPrivacyLoading = false, primaryAction = PrimaryActionUiState( label = buttonState.label.text ?: buttonState.progressPercentText ?: buttonState.label.resId?.let { stringResource(id = it) } ?: "", enabled = buttonState.enabled, Loading @@ -633,6 +632,7 @@ private fun Application.toSearchResultUiState(buttonState: InstallButtonState): isFilledStyle = buttonState.style == InstallButtonStyle.AccentFill, showMore = false, actionIntent = buttonState.actionIntent, progressFraction = buttonState.progressFraction, ), iconUrl = iconUrl, placeholderResId = null, Loading app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt +3 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ data class InstallButtonState( val style: InstallButtonStyle = InstallButtonStyle.AccentOutline, val showProgressBar: Boolean = false, val progressPercentText: String? = null, val progressFraction: Float = 0f, val actionIntent: InstallButtonAction = InstallButtonAction.NoOp, @StringRes val snackbarMessageId: Int? = null, val dialogType: InstallDialogType? = null, Loading Loading @@ -222,8 +223,9 @@ fun mapAppToInstallState( ) Status.DOWNLOADING, Status.DOWNLOADED -> InstallButtonState( label = ButtonLabel(resId = if (percentLabel == null) R.string.cancel else null, text = percentLabel), label = ButtonLabel(resId = R.string.cancel), progressPercentText = percentLabel, progressFraction = progressPercent?.coerceIn(0, 100)?.div(100f) ?: 0f, enabled = true, style = styleFor(status, enabled = true), actionIntent = InstallButtonAction.CancelDownload, Loading Loading
app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultListItem.kt +42 −41 Original line number Diff line number Diff line Loading @@ -19,13 +19,14 @@ package foundation.e.apps.ui.compose.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement Loading @@ -33,6 +34,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding Loading @@ -42,8 +44,6 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Star import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface Loading Loading @@ -277,19 +277,18 @@ private fun PrimaryActionArea( val accentColor = MaterialTheme.colorScheme.tertiary val outlineColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.25f) val animationDuration = 250 val hasProgress = uiState.progressFraction > 0f val targetContainerColor = when { hasProgress -> accentColor.copy(alpha = 0.12f) uiState.isFilledStyle && uiState.enabled -> accentColor uiState.isFilledStyle -> accentColor.copy(alpha = 0.12f) else -> Color.Transparent } val targetBorderColor = when { uiState.isFilledStyle -> Color.Transparent else -> outlineColor } val targetTextColor = when { hasProgress && uiState.progressFraction >= 0.5f -> MaterialTheme.colorScheme.onPrimary hasProgress -> accentColor uiState.isFilledStyle -> MaterialTheme.colorScheme.onPrimary else -> accentColor } Loading @@ -300,41 +299,51 @@ private fun PrimaryActionArea( label = "containerColor" ) val animatedBorderColor = animateColorAsState( targetValue = targetBorderColor, animationSpec = tween(durationMillis = animationDuration), label = "borderColor" ) val animatedTextColor = animateColorAsState( targetValue = targetTextColor, animationSpec = tween(durationMillis = animationDuration), label = "textColor" ) val animatedProgress = animateFloatAsState( targetValue = uiState.progressFraction.coerceIn(0f, 1f), animationSpec = tween(durationMillis = animationDuration), label = "progressFraction" ) Column(horizontalAlignment = Alignment.End) { Button( onClick = onPrimaryClick, enabled = uiState.enabled, val buttonShape = RoundedCornerShape(4.dp) val showBorder = !hasProgress && !uiState.isFilledStyle val borderColor = outlineColor Box( modifier = Modifier .widthIn(min = 88.dp) .height(40.dp), shape = RoundedCornerShape(4.dp), colors = ButtonDefaults.buttonColors( containerColor = animatedContainerColor.value, contentColor = animatedTextColor.value, disabledContainerColor = animatedContainerColor.value, disabledContentColor = animatedTextColor.value.copy(alpha = 0.38f), ), border = BorderStroke(1.dp, animatedBorderColor.value), contentPadding = ButtonDefaults.ContentPadding, .then( if (hasProgress) Modifier.width(88.dp) else Modifier.widthIn(min = 88.dp) ) .height(40.dp) .clip(buttonShape) .background(animatedContainerColor.value) .then( if (showBorder) Modifier.border(1.dp, borderColor, buttonShape) else Modifier ) .clickable(enabled = uiState.enabled, onClick = onPrimaryClick), contentAlignment = Alignment.Center, ) { if (hasProgress) { Box( modifier = Modifier .fillMaxHeight() .fillMaxWidth(animatedProgress.value.coerceIn(0f, 1f)) .align(Alignment.CenterStart) .background(accentColor) ) } val showSpinner = uiState.isInProgress && uiState.label.isBlank() val isProgressLabel = uiState.label.endsWith("%") // Stable keys here val animationKey = when { showSpinner -> "spinner" isProgressLabel -> "progress" else -> uiState.label } AnimatedContent( Loading @@ -354,16 +363,6 @@ private fun PrimaryActionArea( ) } "progress" -> { Text( text = uiState.label, maxLines = 1, overflow = TextOverflow.Clip, color = animatedTextColor.value, style = MaterialTheme.typography.labelLarge, ) } else -> { Text( text = targetKey, Loading @@ -371,6 +370,7 @@ private fun PrimaryActionArea( overflow = TextOverflow.Clip, color = animatedTextColor.value, style = MaterialTheme.typography.labelLarge, modifier = Modifier.padding(horizontal = 16.dp), ) } } Loading Loading @@ -423,6 +423,7 @@ data class PrimaryActionUiState( val isFilledStyle: Boolean, val showMore: Boolean = false, val actionIntent: InstallButtonAction = InstallButtonAction.NoOp, val progressFraction: Float = 0f, ) // --- Previews --- Loading
app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +1 −1 Original line number Diff line number Diff line Loading @@ -625,7 +625,6 @@ private fun Application.toSearchResultUiState(buttonState: InstallButtonState): isPrivacyLoading = false, primaryAction = PrimaryActionUiState( label = buttonState.label.text ?: buttonState.progressPercentText ?: buttonState.label.resId?.let { stringResource(id = it) } ?: "", enabled = buttonState.enabled, Loading @@ -633,6 +632,7 @@ private fun Application.toSearchResultUiState(buttonState: InstallButtonState): isFilledStyle = buttonState.style == InstallButtonStyle.AccentFill, showMore = false, actionIntent = buttonState.actionIntent, progressFraction = buttonState.progressFraction, ), iconUrl = iconUrl, placeholderResId = null, Loading
app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt +3 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ data class InstallButtonState( val style: InstallButtonStyle = InstallButtonStyle.AccentOutline, val showProgressBar: Boolean = false, val progressPercentText: String? = null, val progressFraction: Float = 0f, val actionIntent: InstallButtonAction = InstallButtonAction.NoOp, @StringRes val snackbarMessageId: Int? = null, val dialogType: InstallDialogType? = null, Loading Loading @@ -222,8 +223,9 @@ fun mapAppToInstallState( ) Status.DOWNLOADING, Status.DOWNLOADED -> InstallButtonState( label = ButtonLabel(resId = if (percentLabel == null) R.string.cancel else null, text = percentLabel), label = ButtonLabel(resId = R.string.cancel), progressPercentText = percentLabel, progressFraction = progressPercent?.coerceIn(0, 100)?.div(100f) ?: 0f, enabled = true, style = styleFor(status, enabled = true), actionIntent = InstallButtonAction.CancelDownload, Loading