fix(upgrade): Theme the upgrade retry button for its error card

The retry sits inside the errorContainer card but drew itself with the
default primary-on-surface outlined colors, which clashes with the card and
loses contrast once the tap latch disables it. Content and border now follow
onErrorContainer, with a dimmed disabled pair, and both states get a
preview.
This commit is contained in:
darken
2026-08-03 20:28:01 +02:00
committed by Matthias Urhahn
parent 375af943ae
commit 6fcc2c4348
2 changed files with 73 additions and 1 deletions
@@ -5,12 +5,14 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.AutoAwesome
import androidx.compose.material.icons.twotone.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
@@ -397,6 +399,7 @@ private fun UpgradeOffersBox(
// the user re-run the offer queries instead of leaving a dead screen.
// No reset needed: this composable unmounts the moment the state leaves Unavailable.
var retryTapped by remember { mutableStateOf(false) }
val retryEnabled = !retryTapped
OutlinedButton(
// Guard inside the callback, not just via `enabled`: `enabled` only takes effect
// after recomposition, so two taps in the same frame would both fire.
@@ -406,10 +409,20 @@ private fun UpgradeOffersBox(
onRetry()
}
},
enabled = !retryTapped,
enabled = retryEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(UpgradeScreenTags.GPLAY_RETRY),
// The button sits on the errorContainer card, so the default primary-on-surface
// outlined colors read as a foreign element with poor contrast.
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
disabledContentColor = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.38f),
),
border = BorderStroke(
1.dp,
MaterialTheme.colorScheme.onErrorContainer.copy(alpha = if (retryEnabled) 1f else 0.1f),
),
) {
Text(stringResource(R.string.general_retry_action))
}
@@ -2,6 +2,7 @@ package eu.darken.capod.common.upgrade.ui
import androidx.annotation.StringRes
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -21,6 +22,8 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.ArrowBack
import androidx.compose.material.icons.twotone.CheckCircle
import androidx.compose.material.icons.twotone.WarningAmber
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
@@ -28,6 +31,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
@@ -639,3 +643,58 @@ internal fun UpgradeInlineStateCard(
content()
}
}
@Preview2
@Composable
private fun UpgradeInlineStateCardPreview() {
PreviewWrapper {
Column(modifier = Modifier.padding(16.dp)) {
UpgradeInlineStateCard(
title = "Offers unavailable",
body = "Google Play did not answer. This is usually temporary.",
icon = Icons.TwoTone.WarningAmber,
) {
OutlinedButton(
onClick = {},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
disabledContentColor = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.38f),
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.onErrorContainer),
) {
Text("Retry")
}
}
}
}
}
// The latched state the card shows after the retry was tapped: the contrast of a DISABLED button on
// errorContainer is the part the default theming gets wrong, so it needs its own preview.
@Preview2
@Composable
private fun UpgradeInlineStateCardDisabledActionPreview() {
PreviewWrapper {
Column(modifier = Modifier.padding(16.dp)) {
UpgradeInlineStateCard(
title = "Offers unavailable",
body = "Google Play did not answer. This is usually temporary.",
icon = Icons.TwoTone.WarningAmber,
) {
OutlinedButton(
onClick = {},
enabled = false,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
disabledContentColor = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.38f),
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.1f)),
) {
Text("Retry")
}
}
}
}
}