From 6fcc2c434879ebf10a3cd03043c3595a79cd5d28 Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 3 Aug 2026 19:50:01 +0200 Subject: [PATCH] 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. --- .../capod/common/upgrade/ui/UpgradeScreen.kt | 15 ++++- .../capod/common/upgrade/ui/UpgradeContent.kt | 59 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app/src/gplay/java/eu/darken/capod/common/upgrade/ui/UpgradeScreen.kt b/app/src/gplay/java/eu/darken/capod/common/upgrade/ui/UpgradeScreen.kt index 21539bda..4ae4d654 100644 --- a/app/src/gplay/java/eu/darken/capod/common/upgrade/ui/UpgradeScreen.kt +++ b/app/src/gplay/java/eu/darken/capod/common/upgrade/ui/UpgradeScreen.kt @@ -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)) } diff --git a/app/src/main/java/eu/darken/capod/common/upgrade/ui/UpgradeContent.kt b/app/src/main/java/eu/darken/capod/common/upgrade/ui/UpgradeContent.kt index a40bfcdf..565c12aa 100644 --- a/app/src/main/java/eu/darken/capod/common/upgrade/ui/UpgradeContent.kt +++ b/app/src/main/java/eu/darken/capod/common/upgrade/ui/UpgradeContent.kt @@ -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") + } + } + } + } +}