From c13dbc5bfd5bd12d56f3bd5bf0e6055374a60e61 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 11 Jul 2026 19:40:08 -0500 Subject: [PATCH] refactor(home): extract primary/activation cards -> components/HomePrimaryCards.kt Step 7/9. Verbatim move (activation + primary hero public, benefit-pill private). Co-Authored-By: Claude Fable 5 --- .../java/app/closer/ui/home/HomeScreen.kt | 354 +--------------- .../ui/home/components/HomePrimaryCards.kt | 397 ++++++++++++++++++ 2 files changed, 399 insertions(+), 352 deletions(-) create mode 100644 app/src/main/java/app/closer/ui/home/components/HomePrimaryCards.kt diff --git a/app/src/main/java/app/closer/ui/home/HomeScreen.kt b/app/src/main/java/app/closer/ui/home/HomeScreen.kt index f3593ec2..3290cd47 100644 --- a/app/src/main/java/app/closer/ui/home/HomeScreen.kt +++ b/app/src/main/java/app/closer/ui/home/HomeScreen.kt @@ -96,6 +96,8 @@ import app.closer.ui.home.components.HomeGlyphIcon import app.closer.ui.home.components.HomePill import app.closer.ui.home.components.HomeStatusStrip import app.closer.ui.home.components.LoadingHomeCard +import app.closer.ui.home.components.PartnerActivationCard +import app.closer.ui.home.components.PrimaryHomeActionCard import app.closer.ui.home.components.WaitingForYouSection import app.closer.ui.home.components.actionColors import app.closer.ui.home.components.homeActionGlyph @@ -731,358 +733,6 @@ private fun PartnerSheetAction( private fun formatMonthYear(millis: Long): String = java.text.SimpleDateFormat("MMM yyyy", java.util.Locale.getDefault()).format(java.util.Date(millis)) -@Composable -private fun PartnerActivationCard( - onInvite: () -> Unit, - onAcceptInvite: () -> Unit -) { - val isDark = isCloserDarkTheme() - val inviteTone = HomeActionTone.Invite.actionColors() - CloserCard( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(CloserRadii.FeatureCard), - containerColor = closerCardColor(alpha = 0.94f), - elevation = CloserElevations.Feature - ) { - Column( - modifier = Modifier - .background( - Brush.linearGradient( - if (isDark) { - listOf( - MaterialTheme.colorScheme.surface, - MaterialTheme.colorScheme.surfaceVariant, - MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.72f) - ) - } else { - listOf( - MaterialTheme.colorScheme.surface, - CloserPalette.PurpleSoft, - CloserPalette.PinkMist.copy(alpha = 0.84f) - ) - }, - start = Offset.Zero, - end = Offset.Infinite - ) - ) - .padding(20.dp), - verticalArrangement = Arrangement.spacedBy(18.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - HomePill("1 of 2 connected") - Surface( - shape = RoundedCornerShape(CloserRadii.Pill), - color = MaterialTheme.colorScheme.surface.copy(alpha = 0.76f) - ) { - Row( - modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(5.dp), - verticalAlignment = Alignment.CenterVertically - ) { - HomeGlyphIcon( - resId = R.drawable.glyph_lock, - contentDescription = null, - tint = inviteTone.deep, - modifier = Modifier.size(14.dp) - ) - Text( - text = "Private invite", - style = MaterialTheme.typography.labelSmall, - color = inviteTone.deep, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } - - BrandIllustration( - res = R.drawable.illustration_partner_activation, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxWidth() - .height(142.dp) - .clip(RoundedCornerShape(22.dp)) - ) - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "A private space for two", - style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), - color = MaterialTheme.colorScheme.onSurface, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - Text( - text = "Invite your partner to unlock shared reveals, games, streaks, and answers you can both respond to.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 4, - overflow = TextOverflow.Ellipsis - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - ActivationBenefitPill("Private reveals", Modifier.weight(1f)) - ActivationBenefitPill("Shared streak", Modifier.weight(1f)) - ActivationBenefitPill("Games for two", Modifier.weight(1f)) - } - - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - CloserActionButton( - label = "Invite partner", - onClick = onInvite, - modifier = Modifier.fillMaxWidth(), - containerColor = inviteTone.accent, - contentColor = inviteTone.onAccent - ) - CloserActionButton( - label = "Enter code", - onClick = onAcceptInvite, - modifier = Modifier.fillMaxWidth(), - style = CloserButtonStyle.Secondary, - containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.7f), - contentColor = inviteTone.deep - ) - } - } - } -} - -@Composable -private fun ActivationBenefitPill( - label: String, - modifier: Modifier = Modifier -) { - val colors = HomeActionTone.Invite.actionColors() - Surface( - modifier = modifier, - shape = RoundedCornerShape(CloserRadii.Pill), - color = MaterialTheme.colorScheme.surface.copy(alpha = 0.66f) - ) { - Text( - text = label, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 9.dp, vertical = 7.dp), - style = MaterialTheme.typography.labelSmall, - color = colors.deep, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - -@Composable -private fun PrimaryHomeActionCard( - action: HomeAction, - onAction: (HomeAction) -> Unit, - onReminder: () -> Unit, - onReveal: () -> Unit, - onFollowUp: () -> Unit, - dailyQuestionState: DailyQuestionState, - dailyQuestion: Question? -) { - val colors = action.tone.actionColors() - val isDark = isCloserDarkTheme() - // Daily-question art stays people-forward while keeping the two states distinct. - val artRes = if (action.target == HomeActionTarget.DailyQuestion) { - when (dailyQuestionState) { - DailyQuestionState.UNANSWERED -> R.drawable.illustration_tonight_partner_prompt - DailyQuestionState.BOTH_ANSWERED -> R.drawable.illustration_daily_reveal_ready - else -> homePrimaryArt(action.target) - } - } else { - homePrimaryArt(action.target) - } - - // For daily-question actions, route the CTA through the explicit state handlers - // so the same button label maps to the correct next step (answer, remind, - // reveal, or follow-up) even if the action target is still DailyQuestion. - val ctaClick = when (action.target) { - HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { - DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> onReminder - DailyQuestionState.BOTH_ANSWERED -> onReveal - DailyQuestionState.REVEALED -> onFollowUp - else -> { { onAction(action) } } - } - else -> { { onAction(action) } } - } - - val titleOverride = when (action.target) { - HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { - DailyQuestionState.UNANSWERED -> "Tonight's question is ready." - DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "You showed up tonight. Waiting for your partner." - DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> "Your partner answered. Your turn." - DailyQuestionState.BOTH_ANSWERED -> "Your reveal is waiting" - DailyQuestionState.REVEALED -> "You opened a conversation tonight." - } - else -> action.title - } - - val bodyOverride = when (action.target) { - HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { - DailyQuestionState.UNANSWERED -> - dailyQuestion?.text ?: "Answer tonight's question privately, then choose when to share." - DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> - "Your answer is private until they answer too. No pressure — the reveal waits for both of you." - DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> - "Answer to unlock the reveal. Your response stays private until you are ready." - DailyQuestionState.BOTH_ANSWERED -> - "You both answered — open it together when you're ready." - DailyQuestionState.REVEALED -> - "You revealed an answer together. What comes next is up to both of you." - } - else -> action.body - } - - val timeBudgetLabel = when (action.target) { - HomeActionTarget.DailyQuestion -> when (dailyQuestion?.type) { - "scale" -> "~1 min" - "single_choice" -> "~2 min" - "this_or_that" -> "~2 min" - else -> "~3 min" - } - else -> null - } - - CloserCard( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(CloserRadii.FeatureCard), - containerColor = closerCardColor(alpha = 0.92f), - elevation = CloserElevations.Feature - ) { - Column( - modifier = Modifier - .background( - Brush.linearGradient( - if (isDark) { - listOf( - MaterialTheme.colorScheme.surface, - MaterialTheme.colorScheme.surfaceVariant, - colors.soft.copy(alpha = 0.72f) - ) - } else { - listOf( - MaterialTheme.colorScheme.surface, - colors.soft, - CloserPalette.PinkMist.copy(alpha = 0.72f) - ) - }, - start = Offset.Zero, - end = Offset.Infinite - ) - ) - .padding(20.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - HomePill(action.eyebrow) - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - timeBudgetLabel?.let { label -> - HomePill(label) - } - action.metric?.let { HomePill(it) } - } - } - - artRes?.let { res -> - BrandIllustration( - res = res, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxWidth() - .height(150.dp) - .clip(RoundedCornerShape(22.dp)) - ) - } - - if (artRes != null) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = titleOverride, - style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), - color = MaterialTheme.colorScheme.onSurface, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - Text( - text = bodyOverride, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 4, - overflow = TextOverflow.Ellipsis - ) - } - } else { - Row( - horizontalArrangement = Arrangement.spacedBy(14.dp), - verticalAlignment = Alignment.Top - ) { - Surface( - shape = RoundedCornerShape(CloserRadii.Tile), - color = colors.accent.copy(alpha = 0.16f), - modifier = Modifier.size(52.dp) - ) { - Box(contentAlignment = Alignment.Center) { - HomeGlyphIcon( - resId = homeActionGlyph(action.target), - contentDescription = null, - tint = colors.deep, - modifier = Modifier.size(26.dp) - ) - } - } - - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = titleOverride, - style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), - color = MaterialTheme.colorScheme.onSurface, - maxLines = 3, - overflow = TextOverflow.Ellipsis - ) - Text( - text = bodyOverride, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 4, - overflow = TextOverflow.Ellipsis - ) - } - } - } - - CloserActionButton( - label = action.cta, - onClick = ctaClick, - modifier = Modifier.fillMaxWidth(), - containerColor = colors.accent, - contentColor = colors.onAccent - ) - } - } -} @Composable diff --git a/app/src/main/java/app/closer/ui/home/components/HomePrimaryCards.kt b/app/src/main/java/app/closer/ui/home/components/HomePrimaryCards.kt new file mode 100644 index 00000000..1961db71 --- /dev/null +++ b/app/src/main/java/app/closer/ui/home/components/HomePrimaryCards.kt @@ -0,0 +1,397 @@ +package app.closer.ui.home.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import app.closer.R +import app.closer.domain.model.Question +import app.closer.ui.components.BrandIllustration +import app.closer.ui.components.CloserActionButton +import app.closer.ui.components.CloserButtonStyle +import app.closer.ui.components.CloserCard +import app.closer.ui.components.CloserElevations +import app.closer.ui.components.CloserRadii +import app.closer.ui.home.DailyQuestionState +import app.closer.ui.home.HomeAction +import app.closer.ui.home.HomeActionTarget +import app.closer.ui.home.HomeActionTone +import app.closer.ui.theme.CloserPalette +import app.closer.ui.theme.closerCardColor +import app.closer.ui.theme.isCloserDarkTheme + +/** Home's hero cards — the unpaired partner-activation card and the primary daily-question/action + * card (with its per-state title/body/CTA overrides). Extracted from HomeScreen.kt. */ + +@Composable +fun PartnerActivationCard( + onInvite: () -> Unit, + onAcceptInvite: () -> Unit +) { + val isDark = isCloserDarkTheme() + val inviteTone = HomeActionTone.Invite.actionColors() + CloserCard( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(CloserRadii.FeatureCard), + containerColor = closerCardColor(alpha = 0.94f), + elevation = CloserElevations.Feature + ) { + Column( + modifier = Modifier + .background( + Brush.linearGradient( + if (isDark) { + listOf( + MaterialTheme.colorScheme.surface, + MaterialTheme.colorScheme.surfaceVariant, + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.72f) + ) + } else { + listOf( + MaterialTheme.colorScheme.surface, + CloserPalette.PurpleSoft, + CloserPalette.PinkMist.copy(alpha = 0.84f) + ) + }, + start = Offset.Zero, + end = Offset.Infinite + ) + ) + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(18.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + HomePill("1 of 2 connected") + Surface( + shape = RoundedCornerShape(CloserRadii.Pill), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.76f) + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + HomeGlyphIcon( + resId = R.drawable.glyph_lock, + contentDescription = null, + tint = inviteTone.deep, + modifier = Modifier.size(14.dp) + ) + Text( + text = "Private invite", + style = MaterialTheme.typography.labelSmall, + color = inviteTone.deep, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + + BrandIllustration( + res = R.drawable.illustration_partner_activation, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(142.dp) + .clip(RoundedCornerShape(22.dp)) + ) + + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = "A private space for two", + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Text( + text = "Invite your partner to unlock shared reveals, games, streaks, and answers you can both respond to.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 4, + overflow = TextOverflow.Ellipsis + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + ActivationBenefitPill("Private reveals", Modifier.weight(1f)) + ActivationBenefitPill("Shared streak", Modifier.weight(1f)) + ActivationBenefitPill("Games for two", Modifier.weight(1f)) + } + + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + CloserActionButton( + label = "Invite partner", + onClick = onInvite, + modifier = Modifier.fillMaxWidth(), + containerColor = inviteTone.accent, + contentColor = inviteTone.onAccent + ) + CloserActionButton( + label = "Enter code", + onClick = onAcceptInvite, + modifier = Modifier.fillMaxWidth(), + style = CloserButtonStyle.Secondary, + containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.7f), + contentColor = inviteTone.deep + ) + } + } + } +} + +@Composable +private fun ActivationBenefitPill( + label: String, + modifier: Modifier = Modifier +) { + val colors = HomeActionTone.Invite.actionColors() + Surface( + modifier = modifier, + shape = RoundedCornerShape(CloserRadii.Pill), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.66f) + ) { + Text( + text = label, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 9.dp, vertical = 7.dp), + style = MaterialTheme.typography.labelSmall, + color = colors.deep, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +fun PrimaryHomeActionCard( + action: HomeAction, + onAction: (HomeAction) -> Unit, + onReminder: () -> Unit, + onReveal: () -> Unit, + onFollowUp: () -> Unit, + dailyQuestionState: DailyQuestionState, + dailyQuestion: Question? +) { + val colors = action.tone.actionColors() + val isDark = isCloserDarkTheme() + // Daily-question art stays people-forward while keeping the two states distinct. + val artRes = if (action.target == HomeActionTarget.DailyQuestion) { + when (dailyQuestionState) { + DailyQuestionState.UNANSWERED -> R.drawable.illustration_tonight_partner_prompt + DailyQuestionState.BOTH_ANSWERED -> R.drawable.illustration_daily_reveal_ready + else -> homePrimaryArt(action.target) + } + } else { + homePrimaryArt(action.target) + } + + // For daily-question actions, route the CTA through the explicit state handlers + // so the same button label maps to the correct next step (answer, remind, + // reveal, or follow-up) even if the action target is still DailyQuestion. + val ctaClick = when (action.target) { + HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { + DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> onReminder + DailyQuestionState.BOTH_ANSWERED -> onReveal + DailyQuestionState.REVEALED -> onFollowUp + else -> { { onAction(action) } } + } + else -> { { onAction(action) } } + } + + val titleOverride = when (action.target) { + HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { + DailyQuestionState.UNANSWERED -> "Tonight's question is ready." + DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "You showed up tonight. Waiting for your partner." + DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> "Your partner answered. Your turn." + DailyQuestionState.BOTH_ANSWERED -> "Your reveal is waiting" + DailyQuestionState.REVEALED -> "You opened a conversation tonight." + } + else -> action.title + } + + val bodyOverride = when (action.target) { + HomeActionTarget.DailyQuestion -> when (dailyQuestionState) { + DailyQuestionState.UNANSWERED -> + dailyQuestion?.text ?: "Answer tonight's question privately, then choose when to share." + DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> + "Your answer is private until they answer too. No pressure — the reveal waits for both of you." + DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> + "Answer to unlock the reveal. Your response stays private until you are ready." + DailyQuestionState.BOTH_ANSWERED -> + "You both answered — open it together when you're ready." + DailyQuestionState.REVEALED -> + "You revealed an answer together. What comes next is up to both of you." + } + else -> action.body + } + + val timeBudgetLabel = when (action.target) { + HomeActionTarget.DailyQuestion -> when (dailyQuestion?.type) { + "scale" -> "~1 min" + "single_choice" -> "~2 min" + "this_or_that" -> "~2 min" + else -> "~3 min" + } + else -> null + } + + CloserCard( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(CloserRadii.FeatureCard), + containerColor = closerCardColor(alpha = 0.92f), + elevation = CloserElevations.Feature + ) { + Column( + modifier = Modifier + .background( + Brush.linearGradient( + if (isDark) { + listOf( + MaterialTheme.colorScheme.surface, + MaterialTheme.colorScheme.surfaceVariant, + colors.soft.copy(alpha = 0.72f) + ) + } else { + listOf( + MaterialTheme.colorScheme.surface, + colors.soft, + CloserPalette.PinkMist.copy(alpha = 0.72f) + ) + }, + start = Offset.Zero, + end = Offset.Infinite + ) + ) + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + HomePill(action.eyebrow) + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + timeBudgetLabel?.let { label -> + HomePill(label) + } + action.metric?.let { HomePill(it) } + } + } + + artRes?.let { res -> + BrandIllustration( + res = res, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + .clip(RoundedCornerShape(22.dp)) + ) + } + + if (artRes != null) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = titleOverride, + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onSurface, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + Text( + text = bodyOverride, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 4, + overflow = TextOverflow.Ellipsis + ) + } + } else { + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.Top + ) { + Surface( + shape = RoundedCornerShape(CloserRadii.Tile), + color = colors.accent.copy(alpha = 0.16f), + modifier = Modifier.size(52.dp) + ) { + Box(contentAlignment = Alignment.Center) { + HomeGlyphIcon( + resId = homeActionGlyph(action.target), + contentDescription = null, + tint = colors.deep, + modifier = Modifier.size(26.dp) + ) + } + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = titleOverride, + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onSurface, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + Text( + text = bodyOverride, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 4, + overflow = TextOverflow.Ellipsis + ) + } + } + } + + CloserActionButton( + label = action.cta, + onClick = ctaClick, + modifier = Modifier.fillMaxWidth(), + containerColor = colors.accent, + contentColor = colors.onAccent + ) + } + } +}