From 76d8746a9b649c9044b1b2b69d15fcf80c2fcded Mon Sep 17 00:00:00 2001 From: null Date: Tue, 14 Jul 2026 22:29:05 -0500 Subject: [PATCH] fix(settings): the paired "Connected with" card rendered muddy and unpolished MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects, both only in the paired state — the unpaired state was already styled correctly, which is why this hid. 1. The card was see-through. `partnerCardColor` used primaryContainer at 52% alpha, and the page behind it is a gradient brush, so the gradient bled up through the card: it read muddy against the crisp profile card directly above (which is 96%), and the card's own content bounds showed as a paler, sharp-edged band inside it — measured, not guessed: a 24-unit jump (218,209,226 -> 242,232,251 -> 217,208,225) across the Row's 18dp content inset. Now 96% like its sibling; the same scanline is flat (240,229,253 -> 241,231,255). Content colours are the container's own on* pair rather than the page's ink, so contrast is correct in both themes. 2. The heart was a bare, outsized glyph. ProfileAvatar's no-photo fallback is a 40dp Icon with no container, so the paired row showed a naked heart unlike anything else on the page — while the *unpaired* branch right below it, and every settings row, use a 48dp tile + 24dp glyph. The avatar is now used only when a real partner photo exists; otherwise it falls back to that same tile. Verified live both fixtures (dark 5554 / light 5556) with pixel probes before and after. theme-scan REVIEW 25 -> 24 (one hardcoded-colour hit removed); CRITICAL/MAJOR unchanged. Unit tests green, 0 FATAL. Filed alongside this round's Pass C results (R32) in ClaudeReport/ClaudeQACoverage. Co-Authored-By: Claude Opus 4.8 --- .../app/closer/ui/settings/SettingsScreen.kt | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/app/closer/ui/settings/SettingsScreen.kt b/app/src/main/java/app/closer/ui/settings/SettingsScreen.kt index ff0a8837..095b8b4e 100644 --- a/app/src/main/java/app/closer/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/app/closer/ui/settings/SettingsScreen.kt @@ -355,17 +355,24 @@ fun SettingsScreen( } } - // Partner card — fully tappable, visually obvious + // Partner card — fully tappable, and deliberately tinted so it reads as the one + // relationship row rather than another settings row. + // The tint must be a near-opaque container: the page behind is a gradient brush, so + // a see-through card (this was `primaryContainer` at 52%) lets that gradient bleed + // up and the card renders muddy — and its own inner content bounds show as a paler + // band against it. The sibling profile card directly above is 96% and never does + // this. Content colours are the container's `on*` pair so contrast is correct in + // both themes rather than borrowed from the page's ink. val partnerCardColor = if (state.isPaired) { - SettingsSoft.copy(alpha = 0.52f) + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.96f) } else if (isCloserDarkTheme()) { - MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.74f) + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.96f) } else { - MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.46f) + MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.96f) } - val partnerAccent = if (state.isPaired) SettingsPrimaryDeep else MaterialTheme.colorScheme.secondary - val partnerContentColor = if (state.isPaired) SettingsInk else MaterialTheme.colorScheme.onSecondaryContainer - val partnerMutedColor = if (state.isPaired) SettingsMuted else MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.72f) + val partnerAccent = if (state.isPaired) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.secondary + val partnerContentColor = if (state.isPaired) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSecondaryContainer + val partnerMutedColor = if (state.isPaired) MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.72f) else MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.72f) Card( onClick = { onNavigate( @@ -386,11 +393,16 @@ fun SettingsScreen( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - if (state.isPaired) { + // A real partner photo shows as the avatar; otherwise fall back to the same + // 48dp tile + 24dp glyph the unpaired state and every settings row use. + // ProfileAvatar's own fallback is a bare 40dp icon with no container, which + // left the paired state showing an outsized naked heart unlike anything + // else on the page. + if (state.isPaired && !state.partnerPhotoUrl.isNullOrBlank()) { ProfileAvatar( imageUrl = state.partnerPhotoUrl, fallbackIcon = CloserGlyphs.Heart, - fallbackTint = SettingsPrimaryDeep + fallbackTint = partnerAccent ) } else { Box( @@ -401,7 +413,7 @@ fun SettingsScreen( contentAlignment = Alignment.Center ) { Icon( - CloserGlyphs.HeartOutline, + if (state.isPaired) CloserGlyphs.Heart else CloserGlyphs.HeartOutline, contentDescription = null, tint = partnerAccent, modifier = Modifier.size(24.dp)