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 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-11 19:40:08 -05:00
parent d324efe182
commit c13dbc5bfd
2 changed files with 399 additions and 352 deletions

View File

@ -96,6 +96,8 @@ import app.closer.ui.home.components.HomeGlyphIcon
import app.closer.ui.home.components.HomePill import app.closer.ui.home.components.HomePill
import app.closer.ui.home.components.HomeStatusStrip import app.closer.ui.home.components.HomeStatusStrip
import app.closer.ui.home.components.LoadingHomeCard 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.WaitingForYouSection
import app.closer.ui.home.components.actionColors import app.closer.ui.home.components.actionColors
import app.closer.ui.home.components.homeActionGlyph import app.closer.ui.home.components.homeActionGlyph
@ -731,358 +733,6 @@ private fun PartnerSheetAction(
private fun formatMonthYear(millis: Long): String = private fun formatMonthYear(millis: Long): String =
java.text.SimpleDateFormat("MMM yyyy", java.util.Locale.getDefault()).format(java.util.Date(millis)) 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 @Composable

View File

@ -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
)
}
}
}