feat: add answer history filter pills with reveal/private badges, DesireSync progress meter and reveal UI, HowWell ring progress and privacy tiles, Paywall feature scavenger hunt, category screen grid polish

This commit is contained in:
null 2026-06-18 03:26:12 -05:00
parent 15c1fbdda0
commit 606d724f12
5 changed files with 497 additions and 71 deletions

View File

@ -2,6 +2,7 @@ package app.closer.ui.answers
import app.closer.ui.theme.closerBackgroundBrush
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -19,11 +20,15 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -48,8 +53,13 @@ import app.closer.core.navigation.AppRoute
import app.closer.domain.model.LocalAnswer
import app.closer.ui.components.CategoryGlyph
import app.closer.ui.components.EmptyState
import app.closer.ui.theme.CloserPalette
import app.closer.ui.questions.displayCategoryName
private enum class AnswerHistoryFilter {
ALL, PRIVATE, REVEALED
}
@Composable
fun AnswerHistoryScreen(
onNavigate: (String) -> Unit = {},
@ -73,6 +83,16 @@ private fun AnswerHistoryContent(
onDelete: (String) -> Unit
) {
var pendingDelete by remember { mutableStateOf<LocalAnswer?>(null) }
var selectedFilter by remember { mutableStateOf(AnswerHistoryFilter.ALL) }
val visibleAnswers = remember(state.answers, selectedFilter) {
state.answers.filter { answer ->
when (selectedFilter) {
AnswerHistoryFilter.ALL -> true
AnswerHistoryFilter.PRIVATE -> !answer.isRevealed
AnswerHistoryFilter.REVEALED -> answer.isRevealed
}
}
}
pendingDelete?.let { answer ->
AlertDialog(
@ -146,7 +166,25 @@ private fun AnswerHistoryContent(
)
}
} else {
items(state.answers, key = { it.questionId }) { answer ->
item {
HistoryFilterRow(
selected = selectedFilter,
privateCount = state.answers.count { !it.isRevealed },
revealedCount = state.answers.count { it.isRevealed },
onSelected = { selectedFilter = it }
)
}
if (visibleAnswers.isEmpty()) {
item {
EmptyState(
title = "Nothing in this view",
body = "Switch filters to see the rest of your saved answers."
)
}
}
items(visibleAnswers, key = { it.questionId }) { answer ->
AnswerHistoryCard(
answer = answer,
onClick = { onAnswerSelected(answer) },
@ -158,6 +196,69 @@ private fun AnswerHistoryContent(
}
}
@Composable
private fun HistoryFilterRow(
selected: AnswerHistoryFilter,
privateCount: Int,
revealedCount: Int,
onSelected: (AnswerHistoryFilter) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
HistoryFilterPill(
label = "All",
count = privateCount + revealedCount,
selected = selected == AnswerHistoryFilter.ALL,
onClick = { onSelected(AnswerHistoryFilter.ALL) },
modifier = Modifier.weight(1f)
)
HistoryFilterPill(
label = "Private",
count = privateCount,
selected = selected == AnswerHistoryFilter.PRIVATE,
onClick = { onSelected(AnswerHistoryFilter.PRIVATE) },
modifier = Modifier.weight(1f)
)
HistoryFilterPill(
label = "Revealed",
count = revealedCount,
selected = selected == AnswerHistoryFilter.REVEALED,
onClick = { onSelected(AnswerHistoryFilter.REVEALED) },
modifier = Modifier.weight(1f)
)
}
}
@Composable
private fun HistoryFilterPill(
label: String,
count: Int,
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier
.heightIn(min = 44.dp)
.clickable(onClick = onClick),
shape = RoundedCornerShape(999.dp),
color = if (selected) CloserPalette.PurpleMist else Color.White.copy(alpha = 0.74f),
shadowElevation = if (selected) 2.dp else 0.dp
) {
Text(
text = "$label $count",
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
style = MaterialTheme.typography.labelMedium,
color = if (selected) CloserPalette.PurpleDeep else MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
@ -192,7 +293,7 @@ private fun AnswerHistoryCard(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
HistoryPill(if (answer.isRevealed) "Revealed" else "Private")
HistoryStateBadge(isRevealed = answer.isRevealed)
HistoryPill(answer.category.displayCategoryName())
}
Text(
@ -235,6 +336,35 @@ private fun AnswerHistoryCard(
}
}
@Composable
private fun HistoryStateBadge(isRevealed: Boolean) {
val tint = if (isRevealed) CloserPalette.PurpleDeep else CloserPalette.Evergreen
val container = if (isRevealed) CloserPalette.PurpleMist else CloserPalette.Evergreen.copy(alpha = 0.10f)
Surface(
shape = RoundedCornerShape(999.dp),
color = container
) {
Row(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = if (isRevealed) Icons.Filled.Visibility else Icons.Filled.Lock,
contentDescription = null,
tint = tint,
modifier = Modifier.size(14.dp)
)
Text(
text = if (isRevealed) "Revealed" else "Private",
style = MaterialTheme.typography.labelMedium,
color = tint,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
private fun HistoryPill(label: String) {
Surface(

View File

@ -8,6 +8,7 @@ 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.fillMaxHeight
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -21,6 +22,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
@ -31,7 +33,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
@ -499,11 +500,9 @@ private fun DSAnswerScreen(
}
}
LinearProgressIndicator(
progress = { index.toFloat() / total },
modifier = Modifier.fillMaxWidth(),
color = CloserPalette.Romantic,
trackColor = CloserPalette.Romantic.copy(alpha = 0.15f)
DesireProgressPill(
progress = index.toFloat() / total,
modifier = Modifier.fillMaxWidth()
)
Card(
@ -605,6 +604,7 @@ private fun DSRevealScreen(
textAlign = TextAlign.Center
)
}
DesireRevealMeter(matches = matches.size, total = total)
}
}
@ -660,6 +660,116 @@ private fun DSRevealScreen(
}
}
@Composable
private fun DesireProgressPill(
progress: Float,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.height(8.dp)
.clip(RoundedCornerShape(999.dp))
.background(CloserPalette.Romantic.copy(alpha = 0.15f))
) {
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(progress.coerceIn(0f, 1f))
.clip(RoundedCornerShape(999.dp))
.background(CloserPalette.Romantic)
)
}
}
@Composable
private fun DesireRevealMeter(
matches: Int,
total: Int
) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(22.dp),
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.86f)),
elevation = CardDefaults.cardElevation(0.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
DesirePrivacyTile(
label = "Partner A",
value = "$total private",
modifier = Modifier.weight(1f)
)
StatusGlyph(
icon = Icons.Filled.Sync,
tint = CloserPalette.Romantic,
container = CloserPalette.Romantic.copy(alpha = 0.12f),
size = 38.dp,
iconSize = 20.dp
)
DesirePrivacyTile(
label = "Partner B",
value = "$total private",
modifier = Modifier.weight(1f)
)
}
DesireProgressPill(
progress = matches.toFloat() / total.coerceAtLeast(1),
modifier = Modifier.fillMaxWidth()
)
Text(
text = "$matches shared, ${total - matches} kept private",
style = MaterialTheme.typography.labelMedium,
color = CloserPalette.Romantic,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
@Composable
private fun DesirePrivacyTile(
label: String,
value: String,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
shape = RoundedCornerShape(16.dp),
color = CloserPalette.Romantic.copy(alpha = 0.08f)
) {
Column(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
Text(
text = label,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = value,
style = MaterialTheme.typography.labelMedium,
color = CloserPalette.Romantic,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun DesireMatchCard(match: DesireMatch) {
Card(

View File

@ -2,10 +2,12 @@ package app.closer.ui.howwell
import app.closer.ui.theme.closerCardColor
import android.util.Log
import androidx.compose.foundation.Canvas
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.fillMaxHeight
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -20,6 +22,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Psychology
@ -30,7 +33,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
@ -43,6 +45,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -502,11 +506,9 @@ private fun AnswerScreen(
}
}
LinearProgressIndicator(
progress = { index.toFloat() / total },
modifier = Modifier.fillMaxWidth(),
color = CloserPalette.PurpleDeep,
trackColor = CloserPalette.PurpleMist
HowWellProgressPill(
progress = index.toFloat() / total,
modifier = Modifier.fillMaxWidth()
)
Card(
@ -656,6 +658,12 @@ private fun RevealScreen(
}
}
HowWellScoreStrip(
score = score,
answered = questionNumber,
total = total
)
Text(
text = result.question.text,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
@ -755,18 +763,10 @@ private fun CompleteScreen(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
StatusGlyph(
icon = Icons.Filled.Timeline,
tint = CloserPalette.PurpleDeep,
container = CloserPalette.PurpleMist,
size = 82.dp,
iconSize = 40.dp
)
Text(
text = "$score / $total",
style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Bold),
color = CloserPalette.PurpleDeep,
textAlign = TextAlign.Center
HowWellScoreRing(
score = score,
total = total,
modifier = Modifier.size(118.dp)
)
Text(
text = scoreLabel(score, total),
@ -808,6 +808,110 @@ private fun CompleteScreen(
}
}
@Composable
private fun HowWellProgressPill(
progress: Float,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.height(8.dp)
.clip(RoundedCornerShape(999.dp))
.background(CloserPalette.PurpleMist)
) {
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(progress.coerceIn(0f, 1f))
.clip(RoundedCornerShape(999.dp))
.background(CloserPalette.PurpleDeep)
)
}
}
@Composable
private fun HowWellScoreStrip(
score: Int,
answered: Int,
total: Int
) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
color = CloserPalette.PurpleMist
) {
Row(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
HowWellProgressPill(
progress = score.toFloat() / answered.coerceAtLeast(1),
modifier = Modifier.weight(1f)
)
Text(
text = "$score / $answered read",
style = MaterialTheme.typography.labelMedium,
color = CloserPalette.PurpleDeep,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = "$total total",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun HowWellScoreRing(
score: Int,
total: Int,
modifier: Modifier = Modifier
) {
val progress = if (total == 0) 0f else score.toFloat() / total
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Canvas(modifier = Modifier.fillMaxSize()) {
val strokeWidth = 11.dp.toPx()
drawArc(
color = CloserPalette.PurpleMist,
startAngle = -90f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
)
drawArc(
color = CloserPalette.PurpleDeep,
startAngle = -90f,
sweepAngle = 360f * progress.coerceIn(0f, 1f),
useCenter = false,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "$score",
style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Bold),
color = CloserPalette.PurpleDeep
)
Text(
text = "of $total",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
private fun BreakdownRow(result: HowWellResult) {
val matchColor = Color(0xFF2E7D32)

View File

@ -9,6 +9,8 @@ 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.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -25,6 +27,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
@ -60,6 +63,8 @@ import app.closer.core.navigation.ExternalLinks
import app.closer.domain.repository.BillingState
import app.closer.ui.components.ErrorState
import app.closer.ui.components.LoadingState
import app.closer.ui.components.StatusGlyph
import app.closer.ui.theme.CloserPalette
import com.revenuecat.purchases.Package
private val BENEFITS = listOf(
@ -103,7 +108,6 @@ fun PaywallScreen(
horizontalAlignment = Alignment.CenterHorizontally
) {
HeaderSection(onClose = { onNavigate("back") })
BenefitsCard()
when {
uiState.isLoading -> LoadingState(
@ -124,10 +128,12 @@ fun PaywallScreen(
)
}
BenefitsCard()
if (uiState.purchaseState is BillingState.Loading) {
CircularProgressIndicator(
modifier = Modifier.size(28.dp),
color = Color(0xFFB98AF4)
color = CloserPalette.PurpleRich
)
}
@ -163,9 +169,16 @@ private fun HeaderSection(
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.Top
) {
StatusGlyph(
icon = Icons.Filled.Star,
tint = CloserPalette.PurpleDeep,
container = CloserPalette.PurpleGlow,
size = 58.dp,
iconSize = 28.dp
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Go deeper together",
@ -180,7 +193,7 @@ private fun HeaderSection(
)
}
TextButton(onClick = onClose) {
TextButton(onClick = onClose, modifier = Modifier.size(48.dp)) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
@ -190,45 +203,61 @@ private fun HeaderSection(
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun BenefitsCard(modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
shape = RoundedCornerShape(22.dp),
colors = CardDefaults.cardColors(containerColor = closerCardColor(alpha = 0.88f)),
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 3.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
modifier = Modifier.padding(18.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "What's included",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
color = MaterialTheme.colorScheme.onSurface
)
BENEFITS.forEach { benefit ->
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = Color(0xFF56306F),
modifier = Modifier.size(18.dp)
)
Text(
text = benefit,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
)
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
BENEFITS.forEach { benefit ->
BenefitPill(benefit)
}
}
}
}
}
@Composable
private fun BenefitPill(label: String) {
Row(
modifier = Modifier
.clip(RoundedCornerShape(999.dp))
.background(CloserPalette.PurpleMist)
.padding(horizontal = 10.dp, vertical = 7.dp),
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = CloserPalette.PurpleDeep,
modifier = Modifier.size(15.dp)
)
Text(
text = label,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface
)
}
}
@Composable
private fun PlanOptions(
packages: List<Package>,
@ -239,8 +268,8 @@ private fun PlanOptions(
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFFF4E8FF)),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
colors = CardDefaults.cardColors(containerColor = CloserPalette.PurpleSoft),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
Column(
modifier = Modifier.padding(22.dp),
@ -251,6 +280,11 @@ private fun PlanOptions(
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = "Plans are selected here. Restore and legal links stay below the purchase action.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (packages.isEmpty()) {
Text(
@ -284,7 +318,7 @@ private fun PlanRow(
.fillMaxWidth()
.clip(RoundedCornerShape(18.dp))
.background(
if (isSelected) Color(0xFFB98AF4).copy(alpha = 0.20f)
if (isSelected) CloserPalette.PurpleRich.copy(alpha = 0.20f)
else Color.White.copy(alpha = 0.64f)
)
.selectable(
@ -344,20 +378,41 @@ private fun ActionButtons(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
containerColor = CloserPalette.PurpleDeep,
contentColor = MaterialTheme.colorScheme.onPrimary,
disabledContainerColor = Color(0xFFB98AF4).copy(alpha = 0.40f),
disabledContainerColor = CloserPalette.PurpleRich.copy(alpha = 0.40f),
disabledContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.54f)
)
) {
Text("Continue", fontWeight = FontWeight.SemiBold)
}
TextButton(onClick = onRestore) {
Text(
text = "Restore purchases",
color = Color(0xFF9B8AA6)
)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.54f)),
elevation = CardDefaults.cardElevation(0.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Already subscribed?",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
TextButton(onClick = onRestore) {
Text(
text = "Restore",
color = CloserPalette.PurpleDeep,
fontWeight = FontWeight.SemiBold
)
}
}
}
}
}

View File

@ -288,23 +288,50 @@ private fun FilterPill(
@Composable
private fun DepthHeader(depth: Int, count: Int) {
Row(
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(top = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
shape = RoundedCornerShape(18.dp),
color = Color.White.copy(alpha = 0.68f),
shadowElevation = 0.dp
) {
Text(
text = "Depth $depth",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold
)
CategoryPill("$count ${if (count == 1) "prompt" else "prompts"}")
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 11.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = depthLabel(depth),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = "Depth $depth",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
CategoryPill("$count ${if (count == 1) "prompt" else "prompts"}", emphasis = true)
}
}
}
private fun depthLabel(depth: Int): String = when (depth) {
1 -> "Light openers"
2 -> "Closer prompts"
3 -> "Deeper conversation"
else -> "Depth $depth"
}
@Composable
private fun QuestionListCard(
question: Question,