refactor(theme): full dark mode pass — CloserPalette, Theme, Color, and all surface screens

This commit is contained in:
null 2026-06-23 13:56:27 -05:00
parent 7d3b47b3ba
commit fe1808b36c
16 changed files with 343 additions and 207 deletions

View File

@ -1,6 +1,5 @@
package app.closer.ui.components
import app.closer.ui.theme.CloserPalette
import app.closer.ui.theme.closerCardColor
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
@ -134,7 +133,7 @@ fun CloserActionButton(
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary.copy(alpha = 0.34f)),
colors = ButtonDefaults.outlinedButtonColors(
containerColor = containerColor ?: MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.42f),
contentColor = contentColor ?: CloserPalette.PurpleDeep
contentColor = contentColor ?: MaterialTheme.colorScheme.onPrimaryContainer
)
) {
Text(label)
@ -145,10 +144,10 @@ fun CloserActionButton(
modifier = modifier,
enabled = enabled,
shape = shape,
border = BorderStroke(1.dp, CloserPalette.Danger.copy(alpha = 0.38f)),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.error.copy(alpha = 0.38f)),
colors = ButtonDefaults.outlinedButtonColors(
containerColor = containerColor ?: CloserPalette.PinkMist,
contentColor = contentColor ?: CloserPalette.Danger
containerColor = containerColor ?: MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.18f),
contentColor = contentColor ?: MaterialTheme.colorScheme.error
)
) {
Text(label)
@ -161,7 +160,7 @@ fun CloserPill(
label: String,
modifier: Modifier = Modifier,
containerColor: Color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.55f),
contentColor: Color = CloserPalette.PurpleDeep
contentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer
) {
Surface(
modifier = modifier,

View File

@ -17,6 +17,7 @@ import app.closer.ui.questions.displayCategoryName
import app.closer.ui.theme.CloserPalette
import app.closer.ui.theme.closerBackgroundBrush
import app.closer.ui.theme.closerCardColor
import app.closer.ui.theme.isCloserDarkTheme
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -473,6 +474,8 @@ private fun PartnerActivationCard(
onInvite: () -> Unit,
onAcceptInvite: () -> Unit
) {
val isDark = isCloserDarkTheme()
val inviteTone = HomeActionTone.Invite.actionColors()
CloserCard(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(CloserRadii.FeatureCard),
@ -483,11 +486,19 @@ private fun PartnerActivationCard(
modifier = Modifier
.background(
Brush.linearGradient(
listOf(
MaterialTheme.colorScheme.surface,
CloserPalette.PurpleSoft,
CloserPalette.PinkMist.copy(alpha = 0.84f)
),
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
)
@ -513,13 +524,13 @@ private fun PartnerActivationCard(
Icon(
imageVector = Icons.Filled.Lock,
contentDescription = null,
tint = CloserPalette.PurpleDeep,
tint = inviteTone.deep,
modifier = Modifier.size(14.dp)
)
Text(
text = "Private invite",
style = MaterialTheme.typography.labelSmall,
color = CloserPalette.PurpleDeep,
color = inviteTone.deep,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
maxLines = 1,
@ -550,7 +561,7 @@ private fun PartnerActivationCard(
Text(
text = "Invite your partner to unlock shared reveals, games, streaks, and answers you can both respond to.",
style = MaterialTheme.typography.bodyMedium,
color = Color(0xFF4D4354),
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 4,
overflow = TextOverflow.Ellipsis
)
@ -570,8 +581,8 @@ private fun PartnerActivationCard(
label = "Invite partner",
onClick = onInvite,
modifier = Modifier.fillMaxWidth(),
containerColor = CloserPalette.PurpleDeep,
contentColor = MaterialTheme.colorScheme.surface
containerColor = inviteTone.accent,
contentColor = inviteTone.onAccent
)
CloserActionButton(
label = "Enter code",
@ -579,7 +590,7 @@ private fun PartnerActivationCard(
modifier = Modifier.fillMaxWidth(),
style = CloserButtonStyle.Secondary,
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.7f),
contentColor = CloserPalette.PurpleDeep
contentColor = inviteTone.deep
)
}
}
@ -591,6 +602,7 @@ private fun ActivationBenefitPill(
label: String,
modifier: Modifier = Modifier
) {
val colors = HomeActionTone.Invite.actionColors()
Surface(
modifier = modifier,
shape = RoundedCornerShape(CloserRadii.Pill),
@ -602,7 +614,7 @@ private fun ActivationBenefitPill(
.fillMaxWidth()
.padding(horizontal = 9.dp, vertical = 7.dp),
style = MaterialTheme.typography.labelSmall,
color = CloserPalette.PurpleDeep,
color = colors.deep,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
maxLines = 1,
@ -624,6 +636,7 @@ private fun PrimaryHomeActionCard(
dailyQuestion: Question?
) {
val colors = action.tone.actionColors()
val isDark = isCloserDarkTheme()
// 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,
@ -685,7 +698,19 @@ private fun PrimaryHomeActionCard(
modifier = Modifier
.background(
Brush.linearGradient(
listOf(MaterialTheme.colorScheme.surface, colors.soft, CloserPalette.PinkMist.copy(alpha = 0.72f)),
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
)
@ -748,7 +773,7 @@ private fun PrimaryHomeActionCard(
Text(
text = bodyOverride,
style = MaterialTheme.typography.bodyMedium,
color = Color(0xFF4D4354),
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 4,
overflow = TextOverflow.Ellipsis
)
@ -813,7 +838,7 @@ private fun PulseMetric(
Text(
text = value,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
color = CloserPalette.PurpleDeep,
color = MaterialTheme.colorScheme.primary,
maxLines = 1
)
Text(
@ -926,10 +951,15 @@ private fun SecondaryHomeActionCard(
@Composable
private fun MomentCueCard() {
val isDark = isCloserDarkTheme()
CloserCard(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(CloserRadii.Card),
containerColor = CloserPalette.PinkMist.copy(alpha = 0.7f),
containerColor = if (isDark) {
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.58f)
} else {
CloserPalette.PinkMist.copy(alpha = 0.7f)
},
elevation = CloserElevations.Flat
) {
Column(
@ -960,53 +990,89 @@ private data class HomeActionColors(
val soft: Color,
val accent: Color,
val deep: Color,
val onAccent: Color = Color(0xFF24122F)
val onAccent: Color
)
@Composable
private fun HomeActionTone.actionColors(): HomeActionColors =
when (this) {
private fun HomeActionTone.actionColors(): HomeActionColors {
val scheme = MaterialTheme.colorScheme
if (isCloserDarkTheme()) {
return when (this) {
HomeActionTone.Daily,
HomeActionTone.Ritual,
HomeActionTone.Starter,
HomeActionTone.Pack -> HomeActionColors(
soft = scheme.secondaryContainer,
accent = scheme.secondary,
deep = scheme.secondary,
onAccent = scheme.onSecondary
)
HomeActionTone.Reflection -> HomeActionColors(
soft = scheme.tertiaryContainer,
accent = scheme.tertiary,
deep = scheme.tertiary,
onAccent = scheme.onTertiary
)
else -> HomeActionColors(
soft = scheme.primaryContainer,
accent = scheme.primary,
deep = scheme.primary,
onAccent = scheme.onPrimary
)
}
}
return when (this) {
HomeActionTone.Invite -> HomeActionColors(
soft = CloserPalette.PurpleSoft,
accent = MaterialTheme.colorScheme.primary,
deep = CloserPalette.PurpleDeep
deep = CloserPalette.PurpleDeep,
onAccent = MaterialTheme.colorScheme.onPrimary
)
HomeActionTone.Daily -> HomeActionColors(
soft = CloserPalette.PinkSoft,
accent = CloserPalette.PinkBright,
deep = CloserPalette.PinkAccentDeep
deep = CloserPalette.PinkAccentDeep,
onAccent = Color(0xFF24122F)
)
HomeActionTone.Reflection -> HomeActionColors(
soft = CloserPalette.PurpleGlow,
accent = CloserPalette.PurpleRich,
deep = CloserPalette.PurpleDeep
deep = CloserPalette.PurpleDeep,
onAccent = Color(0xFF24122F)
)
HomeActionTone.Ritual -> HomeActionColors(
soft = CloserPalette.PurpleSoft,
accent = CloserPalette.PinkBright,
deep = CloserPalette.PinkAccentDeep
deep = CloserPalette.PinkAccentDeep,
onAccent = Color(0xFF24122F)
)
HomeActionTone.Starter -> HomeActionColors(
soft = CloserPalette.PinkSoft,
accent = CloserPalette.PinkBright,
deep = CloserPalette.PinkAccentDeep
deep = CloserPalette.PinkAccentDeep,
onAccent = Color(0xFF24122F)
)
HomeActionTone.Pack -> HomeActionColors(
soft = CloserPalette.PinkSoft,
accent = CloserPalette.PinkBright,
deep = CloserPalette.PinkAccentDeep
deep = CloserPalette.PinkAccentDeep,
onAccent = Color(0xFF24122F)
)
HomeActionTone.Utility -> HomeActionColors(
soft = CloserPalette.PurpleSoft,
accent = MaterialTheme.colorScheme.primary,
deep = CloserPalette.PurpleDeep
deep = CloserPalette.PurpleDeep,
onAccent = MaterialTheme.colorScheme.onPrimary
)
HomeActionTone.Pending -> HomeActionColors(
soft = CloserPalette.PurpleSoft,
accent = MaterialTheme.colorScheme.primary,
deep = CloserPalette.PurpleDeep
deep = CloserPalette.PurpleDeep,
onAccent = MaterialTheme.colorScheme.onPrimary
)
}
}
@Composable
private fun CategoryPreviewGrid(
@ -1239,7 +1305,11 @@ private fun EmptyHomeContent(
private fun HomePill(label: String) {
CloserPill(
label = label,
containerColor = CloserPalette.PinkMist.copy(alpha = 0.72f),
containerColor = if (isCloserDarkTheme()) {
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.78f)
} else {
CloserPalette.PinkMist.copy(alpha = 0.72f)
},
contentColor = MaterialTheme.colorScheme.onSurface
)
}

View File

@ -49,7 +49,6 @@ import app.closer.ui.components.CloserClickableCard
import app.closer.ui.components.CloserElevations
import app.closer.ui.components.CloserPill
import app.closer.ui.components.CloserRadii
import app.closer.ui.theme.CloserPalette
import app.closer.ui.theme.closerBackgroundBrush
import app.closer.ui.theme.closerPlayCardBrush
@ -147,7 +146,7 @@ private fun PlayHubContent(
title = "Date Match",
subtitle = "Swipe ideas",
icon = Icons.Filled.Favorite,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f),
onClick = { onNavigate(AppRoute.DATE_MATCH) }
)
@ -155,7 +154,7 @@ private fun PlayHubContent(
title = "Plan Date",
subtitle = "Set the shape",
icon = Icons.Filled.Star,
tint = CloserPalette.Gold,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.weight(1f),
onClick = { onNavigate(AppRoute.DATE_BUILDER) }
)
@ -171,7 +170,7 @@ private fun PlayHubContent(
title = "Bucket List",
subtitle = "Save ideas",
icon = Icons.Filled.Done,
tint = CloserPalette.Evergreen,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.weight(1f),
onClick = { onNavigate(AppRoute.BUCKET_LIST) }
)
@ -179,7 +178,7 @@ private fun PlayHubContent(
title = "Past Games",
subtitle = "All results",
icon = Icons.Filled.Home,
tint = CloserPalette.PurpleDeep,
tint = MaterialTheme.colorScheme.primary,
locked = !hasPremium,
modifier = Modifier.weight(1f),
onClick = { onNavigate(AppRoute.GAME_HISTORY) }
@ -212,14 +211,14 @@ private fun ThisOrThatCard(
) {
Surface(
shape = RoundedCornerShape(CloserRadii.Tile),
color = CloserPalette.PinkMist,
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.78f),
modifier = Modifier.size(52.dp)
) {
Box(contentAlignment = Alignment.Center) {
Text(
text = "A/B",
style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold),
color = CloserPalette.PinkAccentDeep
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
@ -241,8 +240,8 @@ private fun ThisOrThatCard(
)
CloserPill(
label = "10 prompts",
containerColor = CloserPalette.PinkMist,
contentColor = CloserPalette.PinkAccentDeep
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.78f),
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
}
Text(
@ -256,7 +255,7 @@ private fun ThisOrThatCard(
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = CloserPalette.PinkAccentDeep,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(18.dp)
)
}
@ -305,7 +304,7 @@ private fun DesireSyncCard(
)
Surface(
shape = RoundedCornerShape(CloserRadii.Pill),
color = CloserPalette.Romantic.copy(alpha = 0.12f)
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.66f)
) {
Row(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
@ -315,13 +314,13 @@ private fun DesireSyncCard(
Icon(
imageVector = Icons.Filled.Lock,
contentDescription = null,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(13.dp)
)
Text(
text = "Premium",
style = MaterialTheme.typography.labelSmall,
color = CloserPalette.Romantic,
color = MaterialTheme.colorScheme.onSecondaryContainer,
fontWeight = FontWeight.SemiBold
)
}
@ -338,7 +337,7 @@ private fun DesireSyncCard(
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(18.dp)
)
}
@ -388,8 +387,8 @@ private fun HowWellCard(
)
CloserPill(
label = "10 rounds",
containerColor = CloserPalette.Romantic.copy(alpha = 0.12f),
contentColor = CloserPalette.Romantic
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.66f),
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
}
Text(
@ -403,7 +402,7 @@ private fun HowWellCard(
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(18.dp)
)
}
@ -432,7 +431,7 @@ private fun ConnectionChallengesCard(
) {
Surface(
shape = RoundedCornerShape(CloserRadii.Tile),
color = CloserPalette.PurpleDeep.copy(alpha = 0.12f),
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.66f),
modifier = Modifier.size(52.dp)
) {
Box(contentAlignment = Alignment.Center) {
@ -460,8 +459,8 @@ private fun ConnectionChallengesCard(
)
CloserPill(
label = "7 days",
containerColor = CloserPalette.PurpleDeep.copy(alpha = 0.10f),
contentColor = CloserPalette.PurpleDeep
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.66f),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
Text(
@ -475,7 +474,7 @@ private fun ConnectionChallengesCard(
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = CloserPalette.PurpleDeep,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(18.dp)
)
}
@ -504,7 +503,7 @@ private fun MemoryLaneCard(
) {
Surface(
shape = RoundedCornerShape(CloserRadii.Tile),
color = CloserPalette.Romantic.copy(alpha = 0.12f),
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.66f),
modifier = Modifier.size(52.dp)
) {
Box(contentAlignment = Alignment.Center) {
@ -530,7 +529,7 @@ private fun MemoryLaneCard(
)
Surface(
shape = RoundedCornerShape(CloserRadii.Pill),
color = CloserPalette.Romantic.copy(alpha = 0.12f)
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.66f)
) {
Row(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
@ -540,13 +539,13 @@ private fun MemoryLaneCard(
Icon(
imageVector = Icons.Filled.Lock,
contentDescription = null,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(13.dp)
)
Text(
text = "Premium",
style = MaterialTheme.typography.labelSmall,
color = CloserPalette.Romantic,
color = MaterialTheme.colorScheme.onSecondaryContainer,
fontWeight = FontWeight.SemiBold
)
}
@ -563,7 +562,7 @@ private fun MemoryLaneCard(
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = CloserPalette.Romantic,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(18.dp)
)
}
@ -595,12 +594,12 @@ private fun FeaturedPlayCard(
CloserPill(
label = "Wheel",
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.72f),
contentColor = CloserPalette.PurpleDeep
contentColor = MaterialTheme.colorScheme.onSurface
)
CloserPill(
label = "10 prompts",
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = CloserPalette.PinkAccentDeep
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
}
@ -640,8 +639,8 @@ private fun FeaturedPlayCard(
.fillMaxWidth()
.heightIn(min = 54.dp),
style = CloserButtonStyle.Primary,
containerColor = CloserPalette.PurpleDeep,
contentColor = MaterialTheme.colorScheme.surface
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
}
}
@ -702,18 +701,18 @@ private fun CompactPlayCard(
horizontalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.weight(1f)
) {
if (locked) {
if (locked) {
Icon(
imageVector = Icons.Filled.Lock,
contentDescription = null,
tint = CloserPalette.Gold,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.size(11.dp)
)
}
Text(
text = if (locked) "Premium" else subtitle,
style = MaterialTheme.typography.bodySmall,
color = if (locked) CloserPalette.Gold else MaterialTheme.colorScheme.onSurfaceVariant,
color = if (locked) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

View File

@ -1,6 +1,7 @@
package app.closer.ui.questions
import app.closer.ui.theme.closerBackgroundBrush
import app.closer.ui.theme.closerCardColor
import android.provider.Settings
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
@ -153,8 +154,8 @@ fun LocalQuestionContent(
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF24122F)
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Text(
@ -203,7 +204,7 @@ private fun LocalQuestionHeader(
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
color = Color.White.copy(alpha = 0.82f),
color = closerCardColor(alpha = 0.92f),
shadowElevation = 5.dp
) {
Column(
@ -258,7 +259,7 @@ private fun SubmittedAnswerCard(
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(22.dp),
color = Color.White.copy(alpha = 0.78f),
color = closerCardColor(alpha = 0.86f),
shadowElevation = 0.dp
) {
Row(
@ -270,13 +271,13 @@ private fun SubmittedAnswerCard(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(Color(0xFFB98AF4).copy(alpha = 0.18f)),
.background(MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.58f)),
contentAlignment = Alignment.Center
) {
Text(
text = badge,
style = MaterialTheme.typography.labelSmall,
color = Color(0xFF56306F),
color = MaterialTheme.colorScheme.onPrimaryContainer,
fontWeight = FontWeight.Bold
)
}

View File

@ -151,10 +151,10 @@ private fun QuestionCategoryContent(
.heightIn(min = 54.dp),
shape = RoundedCornerShape(18.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF56306F),
contentColor = Color.White,
disabledContainerColor = Color(0xFF56306F).copy(alpha = 0.35f),
disabledContentColor = Color.White.copy(alpha = 0.54f)
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
disabledContainerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.35f),
disabledContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.54f)
)
) {
Text(

View File

@ -1,11 +1,11 @@
package app.closer.ui.questions
import androidx.compose.runtime.Composable
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.tooling.preview.Preview
import app.closer.core.navigation.AppRoute
import app.closer.ui.components.FinishedEmptyStateAction
import app.closer.ui.components.FinishedEmptyStateScreen
import app.closer.ui.theme.CloserPalette
@Composable
fun QuestionComposerScreen(
@ -18,7 +18,7 @@ fun QuestionComposerScreen(
glyphCategoryId = "question",
primaryAction = FinishedEmptyStateAction("Browse packs", AppRoute.QUESTION_PACKS),
secondaryAction = FinishedEmptyStateAction("Daily question", AppRoute.DAILY_QUESTION),
accent = CloserPalette.PurpleDeep,
accent = MaterialTheme.colorScheme.primary,
details = listOf(
"Choose prompts already shaped for real conversation.",
"Keep the next step focused on answering, not drafting.",

View File

@ -184,7 +184,7 @@ private fun QuestionPackLibraryContent(
.padding(top = 6.dp, bottom = 22.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
@ -373,13 +373,14 @@ private fun QuestionPackItem.metadataLabels(): List<String> {
).filterNot { it == "Question" }.distinct()
}
@Composable
private fun packAccent(categoryId: String): Color {
val palette = listOf(
Color(0xFF56306F),
Color(0xFF8A4BC1),
Color(0xFFB98AF4),
Color(0xFFB65F93),
Color(0xFFE7A2D1)
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.secondary,
MaterialTheme.colorScheme.tertiary,
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.secondaryContainer
)
return palette[kotlin.math.abs(categoryId.hashCode()) % palette.size]
}

View File

@ -263,8 +263,8 @@ private fun RevealedPhase(
modifier = Modifier.weight(1f).heightIn(min = 48.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF24122F)
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Text("Next prompt")

View File

@ -47,7 +47,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.closer.R
import app.closer.core.navigation.ExternalLinks
import app.closer.ui.theme.CloserPalette
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -94,7 +93,7 @@ fun PrivacyScreen(
// ── What your partner can see ─────────────────────────────────────
PrivacySectionHeader(
icon = Icons.Default.CheckCircle,
iconTint = CloserPalette.PurpleDeep,
iconTint = SettingsPrimary,
title = stringResource(R.string.privacy_section_partner_visible)
)
@ -139,7 +138,7 @@ fun PrivacyScreen(
// ── What stays private ────────────────────────────────────────────
PrivacySectionHeader(
icon = Icons.Default.Lock,
iconTint = CloserPalette.Romantic,
iconTint = MaterialTheme.colorScheme.secondary,
title = stringResource(R.string.privacy_section_data)
)

View File

@ -69,6 +69,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import app.closer.core.navigation.AppRoute
import app.closer.domain.model.OutcomeDay
import app.closer.ui.components.OutcomeCheckInDialog
import app.closer.ui.theme.isCloserDarkTheme
import app.closer.ui.settings.SettingsDanger
import app.closer.ui.settings.SettingsInk
import app.closer.ui.settings.SettingsMuted
@ -120,11 +121,16 @@ fun SettingsSection(
accent: Color = Color(0xFFF7C8E4),
content: @Composable () -> Unit
) {
val sectionAccent = if (isCloserDarkTheme()) {
MaterialTheme.colorScheme.primaryContainer
} else {
accent
}
Card(
modifier = modifier
.fillMaxWidth(),
shape = RoundedCornerShape(22.dp),
colors = CardDefaults.cardColors(containerColor = accent.copy(alpha = 0.16f)),
colors = CardDefaults.cardColors(containerColor = sectionAccent.copy(alpha = 0.16f)),
elevation = CardDefaults.cardElevation(defaultElevation = 5.dp)
) {
Column(

View File

@ -21,6 +21,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -129,14 +130,16 @@ class SettingsViewModel @Inject constructor(
private fun observeCurrentUser(userId: String) {
currentUserJob?.cancel()
currentUserJob = viewModelScope.launch {
userRepository.observeUser(userId).collect { user ->
_uiState.update {
it.copy(
displayName = user?.displayName ?: it.displayName,
photoUrl = user?.photoUrl ?: ""
)
userRepository.observeUser(userId)
.catch { error -> Log.w(TAG, "Current user observer stopped", error) }
.collect { user ->
_uiState.update {
it.copy(
displayName = user?.displayName ?: it.displayName,
photoUrl = user?.photoUrl ?: ""
)
}
}
}
}
}
@ -147,14 +150,16 @@ class SettingsViewModel @Inject constructor(
return
}
partnerUserJob = viewModelScope.launch {
userRepository.observeUser(partnerId).collect { partner ->
_uiState.update {
it.copy(
partnerName = partner?.displayName,
partnerPhotoUrl = partner?.photoUrl
)
userRepository.observeUser(partnerId)
.catch { error -> Log.w(TAG, "Partner user observer stopped", error) }
.collect { partner ->
_uiState.update {
it.copy(
partnerName = partner?.displayName,
partnerPhotoUrl = partner?.photoUrl
)
}
}
}
}
}

View File

@ -6,17 +6,30 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import app.closer.ui.theme.isCloserDarkTheme
internal val SettingsBackgroundBrush: Brush
@Composable
get() = Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.secondaryContainer
),
start = Offset.Zero,
end = Offset.Infinite
)
get() {
val colors = if (isCloserDarkTheme()) {
listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.72f)
)
} else {
listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.secondaryContainer
)
}
return Brush.linearGradient(
colors = colors,
start = Offset.Zero,
end = Offset.Infinite
)
}
internal val SettingsInk: Color
@Composable get() = MaterialTheme.colorScheme.onBackground

View File

@ -55,7 +55,6 @@ import app.closer.core.navigation.ExternalLinks
import app.closer.domain.repository.BillingRepository
import app.closer.domain.repository.BillingState
import app.closer.ui.components.LoadingState
import app.closer.ui.theme.CloserPalette
import app.closer.ui.theme.closerBackgroundBrush
import app.closer.ui.theme.closerCardColor
import com.revenuecat.purchases.CustomerInfo
@ -205,7 +204,7 @@ private fun PremiumContent(
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp),
color = CloserPalette.PurpleDeep.copy(alpha = 0.08f),
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.24f),
shadowElevation = 0.dp
) {
Column(
@ -252,7 +251,7 @@ private fun PremiumContent(
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
tint = CloserPalette.PurpleDeep,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(16.dp)
)
Text(
@ -270,8 +269,8 @@ private fun PremiumContent(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CloserPalette.PurpleDeep,
contentColor = Color.White
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Icon(
@ -353,7 +352,7 @@ private fun FreeContent(
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
tint = CloserPalette.PurpleDeep,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(16.dp)
)
Text(
@ -371,8 +370,8 @@ private fun FreeContent(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CloserPalette.PurpleDeep,
contentColor = Color.White
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Text("Upgrade to Premium", fontWeight = FontWeight.SemiBold)

View File

@ -1,11 +1,11 @@
package app.closer.ui.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
object CloserPalette {
val BackgroundWash = Color(0xFFF8F1FF)
@ -28,15 +28,27 @@ object CloserPalette {
@Composable
fun closerBackgroundBrush(): Brush =
Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.secondaryContainer
),
start = Offset.Zero,
end = Offset.Infinite
)
if (isCloserDarkTheme()) {
Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.72f)
),
start = Offset.Zero,
end = Offset.Infinite
)
} else {
Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.background,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.secondaryContainer
),
start = Offset.Zero,
end = Offset.Infinite
)
}
@Composable
fun closerInkColor(): Color = MaterialTheme.colorScheme.onSurface
@ -62,7 +74,7 @@ fun closerSolidCardColor(): Color = MaterialTheme.colorScheme.surface
@Composable
fun closerSoftSurfaceColor(alpha: Float = 0.92f): Color =
if (isSystemInDarkTheme()) {
if (isCloserDarkTheme()) {
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha)
} else {
Color.White.copy(alpha = alpha)
@ -70,7 +82,7 @@ fun closerSoftSurfaceColor(alpha: Float = 0.92f): Color =
@Composable
fun closerSoftPurpleColor(alpha: Float = 1f): Color =
if (isSystemInDarkTheme()) {
if (isCloserDarkTheme()) {
MaterialTheme.colorScheme.primaryContainer.copy(alpha = alpha)
} else {
CloserPalette.PurpleMist.copy(alpha = alpha)
@ -78,7 +90,7 @@ fun closerSoftPurpleColor(alpha: Float = 1f): Color =
@Composable
fun closerSoftPinkColor(alpha: Float = 1f): Color =
if (isSystemInDarkTheme()) {
if (isCloserDarkTheme()) {
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = alpha)
} else {
CloserPalette.PinkMist.copy(alpha = alpha)
@ -91,11 +103,19 @@ fun closerScrimColor(alpha: Float = 0.54f): Color =
@Composable
fun closerPlayCardBrush(): Brush =
Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.secondaryContainer
),
colors = if (isCloserDarkTheme()) {
listOf(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.78f)
)
} else {
listOf(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.secondaryContainer
)
},
start = Offset.Zero,
end = Offset.Infinite
)
@ -106,20 +126,37 @@ fun closerBrandGlyphBrush(): Brush =
colors = listOf(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.secondary,
CloserPalette.PurpleDeep
if (isCloserDarkTheme()) MaterialTheme.colorScheme.tertiary else CloserPalette.PurpleDeep
),
start = Offset.Zero,
end = Offset.Infinite
)
@Composable
fun closerWheelSegmentColors(): List<Color> = listOf(
MaterialTheme.colorScheme.primary,
CloserPalette.PinkWheel,
MaterialTheme.colorScheme.secondary,
CloserPalette.PurpleGlow,
CloserPalette.PurpleRich,
MaterialTheme.colorScheme.secondaryContainer,
CloserPalette.PurpleDeep,
CloserPalette.PinkShell
)
fun closerWheelSegmentColors(): List<Color> =
if (isCloserDarkTheme()) {
listOf(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.secondary,
MaterialTheme.colorScheme.tertiary,
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.secondaryContainer,
MaterialTheme.colorScheme.tertiaryContainer,
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.outline
)
} else {
listOf(
MaterialTheme.colorScheme.primary,
CloserPalette.PinkWheel,
MaterialTheme.colorScheme.secondary,
CloserPalette.PurpleGlow,
CloserPalette.PurpleRich,
MaterialTheme.colorScheme.secondaryContainer,
CloserPalette.PurpleDeep,
CloserPalette.PinkShell
)
}
@Composable
fun isCloserDarkTheme(): Boolean = MaterialTheme.colorScheme.background.luminance() < 0.5f

View File

@ -3,18 +3,18 @@ package app.closer.ui.theme
import androidx.compose.ui.graphics.Color
// Dark theme colors (for reference if needed)
val darkPrimaryColor = Color(0xFFB98AF4)
val darkPrimaryContainerColor = Color(0xFF43255F)
val darkSecondaryColor = Color(0xFFFFAFD9)
val darkTertiaryColor = Color(0xFFB98AF4)
val darkBackgroundColor = Color(0xFF18111E)
val darkSurfaceColor = Color(0xFF211729)
val darkErrorColor = Color(0xFFFFB3BA)
val darkPrimaryColor = Color(0xFFD7BBFF)
val darkPrimaryContainerColor = Color(0xFF4D3865)
val darkSecondaryColor = Color(0xFFE8B7D0)
val darkTertiaryColor = Color(0xFFC9C1FF)
val darkBackgroundColor = Color(0xFF111015)
val darkSurfaceColor = Color(0xFF19161F)
val darkErrorColor = Color(0xFFFFB4AB)
val darkOnPrimaryColor = Color(0xFFFFFFFF)
val darkOnPrimaryContainerColor = Color(0xFFF3E8FF)
val darkOnSecondaryColor = Color(0xFFFFFFFF)
val darkOnTertiaryColor = Color(0xFF24122F)
val darkOnBackgroundColor = Color(0xFFF2E8F6)
val darkOnSurfaceColor = Color(0xFFF2E8F6)
val darkOnErrorColor = Color(0xFFFFFFFF)
val darkOnPrimaryColor = Color(0xFF37204E)
val darkOnPrimaryContainerColor = Color(0xFFF2E8FF)
val darkOnSecondaryColor = Color(0xFF482638)
val darkOnTertiaryColor = Color(0xFF302A55)
val darkOnBackgroundColor = Color(0xFFEDE7EF)
val darkOnSurfaceColor = Color(0xFFEDE7EF)
val darkOnErrorColor = Color(0xFF690005)

View File

@ -22,25 +22,25 @@ fun CloserTheme(
)
}
// Purple-pink color palette (Light theme)
val PrimaryColor = Color(0xFFB98AF4)
val PrimaryContainerColor = Color(0xFFF3E8FF)
val SecondaryColor = Color(0xFFE7A2D1)
val TertiaryColor = Color(0xFFB98AF4)
val BackgroundColor = Color(0xFFFFFBFE)
val SurfaceColor = Color(0xFFFFFBFE)
val ErrorColor = Color(0xFF8D2D35)
val SurfaceVariantColor = Color(0xFFF4E8FF)
val OutlineColor = Color(0xFF9B8AA6)
val OutlineVariantColor = Color(0xFFE3D4EB)
// Brand palette. Keep accent color in containers and controls; large surfaces stay quiet.
val PrimaryColor = Color(0xFF7B4DB2)
val PrimaryContainerColor = Color(0xFFF1E7FF)
val SecondaryColor = Color(0xFF9B4F78)
val TertiaryColor = Color(0xFF6B5AA6)
val BackgroundColor = Color(0xFFFFFBFF)
val SurfaceColor = Color(0xFFFFFBFF)
val ErrorColor = Color(0xFFBA1A1A)
val SurfaceVariantColor = Color(0xFFF0E8F4)
val OutlineColor = Color(0xFF807581)
val OutlineVariantColor = Color(0xFFD2C7D5)
val OnPrimaryColor = Color(0xFF24122F)
val OnPrimaryContainerColor = Color(0xFF321545)
val OnSecondaryColor = Color(0xFF2E1731)
val OnTertiaryColor = Color(0xFF24122F)
val OnBackgroundColor = Color(0xFF261D2E)
val OnSurfaceColor = Color(0xFF261D2E)
val OnSurfaceVariantColor = Color(0xFF5A5060)
val OnPrimaryColor = Color(0xFFFFFFFF)
val OnPrimaryContainerColor = Color(0xFF29123F)
val OnSecondaryColor = Color(0xFFFFFFFF)
val OnTertiaryColor = Color(0xFFFFFFFF)
val OnBackgroundColor = Color(0xFF211A24)
val OnSurfaceColor = Color(0xFF211A24)
val OnSurfaceVariantColor = Color(0xFF514957)
val OnErrorColor = Color(0xFFFFFFFF)
val lightColors = lightColorScheme(
@ -50,11 +50,11 @@ val lightColors = lightColorScheme(
onPrimaryContainer = OnPrimaryContainerColor,
secondary = SecondaryColor,
onSecondary = OnSecondaryColor,
secondaryContainer = Color(0xFFFFE8F4),
secondaryContainer = Color(0xFFFFD8E9),
onSecondaryContainer = Color(0xFF3B1730),
tertiary = TertiaryColor,
onTertiary = OnTertiaryColor,
tertiaryContainer = Color(0xFFF4E8FF),
tertiaryContainer = Color(0xFFE9E3FF),
onTertiaryContainer = Color(0xFF321545),
background = BackgroundColor,
surface = SurfaceColor,
@ -69,26 +69,33 @@ val lightColors = lightColorScheme(
)
val darkColors = darkColorScheme(
primary = Color(0xFFCFA7FF),
onPrimary = Color(0xFF2A1238),
primaryContainer = Color(0xFF43255F),
onPrimaryContainer = Color(0xFFF3E8FF),
secondary = Color(0xFFFFAFD9),
onSecondary = Color(0xFF3B1730),
secondaryContainer = Color(0xFF5B2847),
onSecondaryContainer = Color(0xFFFFD8EB),
tertiary = Color(0xFFB98AF4),
onTertiary = Color(0xFF24122F),
tertiaryContainer = Color(0xFF43255F),
onTertiaryContainer = Color(0xFFF3E8FF),
background = Color(0xFF18111E),
surface = Color(0xFF211729),
onBackground = Color(0xFFF2E8F6),
onSurface = Color(0xFFF2E8F6),
surfaceVariant = Color(0xFF372641),
onSurfaceVariant = Color(0xFFD9C8E2),
outline = Color(0xFFA996B4),
outlineVariant = Color(0xFF5A4666),
error = Color(0xFFFFB3BA),
onError = OnErrorColor
primary = Color(0xFFD7BBFF),
onPrimary = Color(0xFF37204E),
primaryContainer = Color(0xFF4D3865),
onPrimaryContainer = Color(0xFFF2E8FF),
secondary = Color(0xFFE8B7D0),
onSecondary = Color(0xFF482638),
secondaryContainer = Color(0xFF3D2C36),
onSecondaryContainer = Color(0xFFFFD8EA),
tertiary = Color(0xFFC9C1FF),
onTertiary = Color(0xFF302A55),
tertiaryContainer = Color(0xFF343047),
onTertiaryContainer = Color(0xFFE8E2FF),
background = Color(0xFF111015),
surface = Color(0xFF19161F),
onBackground = Color(0xFFEDE7EF),
onSurface = Color(0xFFEDE7EF),
surfaceVariant = Color(0xFF2D2933),
onSurfaceVariant = Color(0xFFD3C8D8),
outline = Color(0xFF9E93A4),
outlineVariant = Color(0xFF4D4653),
error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
inverseSurface = Color(0xFFEDE7EF),
inverseOnSurface = Color(0xFF322F36),
inversePrimary = Color(0xFF6D45A0),
surfaceTint = Color(0xFFD7BBFF),
scrim = Color(0xFF000000)
)