fix(theme): apply consistent color system, polish UI across all screens

This commit is contained in:
null 2026-06-16 02:49:36 -05:00
parent c1548f28fb
commit 5302526d32
21 changed files with 437 additions and 121 deletions

View File

@ -2,15 +2,22 @@ package com.couplesconnect.app.core.navigation
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -20,6 +27,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.navArgument
import com.couplesconnect.app.ui.auth.ForgotPasswordScreen
import com.couplesconnect.app.ui.answers.AnswerHistoryScreen
@ -53,6 +61,7 @@ import com.couplesconnect.app.ui.wheel.WheelCompleteScreen
import com.couplesconnect.app.ui.wheel.WheelHistoryScreen
import com.couplesconnect.app.ui.wheel.WheelSessionScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppNavigation(
modifier: Modifier = Modifier,
@ -61,17 +70,54 @@ fun AppNavigation(
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val bottomRoutes = topLevelRoutes.map { it.route }.toSet()
val bottomRoutes = AppRoute.topLevelRoutes
val shellTitle = currentRoute
?.takeIf { it in shellBackRoutes }
?.let(AppRoute::titleFor)
val navigateRoute: (String) -> Unit = { route ->
if (route == "back") {
navController.popBackStack()
} else {
navController.navigate(route)
}
}
Scaffold(
modifier = modifier,
topBar = {
if (shellTitle != null) {
TopAppBar(
title = {
Text(
text = shellTitle,
style = MaterialTheme.typography.titleLarge
)
},
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background.copy(alpha = 0.96f)
)
)
}
},
bottomBar = {
if (currentRoute in bottomRoutes) {
AppBottomNavigation(
currentRoute = currentRoute,
onRouteSelected = { route ->
navController.navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
@ -85,37 +131,37 @@ fun AppNavigation(
) {
// Onboarding
composable(route = AppRoute.ONBOARDING) {
OnboardingScreen(onNavigate = navController::navigate)
OnboardingScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.CREATE_PROFILE) {
CreateProfileScreen(onNavigate = navController::navigate)
CreateProfileScreen(onNavigate = navigateRoute)
}
// Auth
composable(route = AppRoute.LOGIN) {
LoginScreen(onNavigate = navController::navigate)
LoginScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.SIGN_UP) {
SignUpScreen(onNavigate = navController::navigate)
SignUpScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.FORGOT_PASSWORD) {
ForgotPasswordScreen(onNavigate = navController::navigate)
ForgotPasswordScreen(onNavigate = navigateRoute)
}
// Home
composable(route = AppRoute.HOME) {
HomeScreen(onNavigate = navController::navigate)
HomeScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.PARTNER_HOME) {
PartnerHomeScreen(onNavigate = navController::navigate)
PartnerHomeScreen(onNavigate = navigateRoute)
}
// Daily Question
composable(route = AppRoute.DAILY_QUESTION) {
DailyQuestionScreen(onNavigate = navController::navigate)
DailyQuestionScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.QUESTION_PACKS) {
QuestionPackLibraryScreen(onNavigate = navController::navigate)
QuestionPackLibraryScreen(onNavigate = navigateRoute)
}
composable(
route = AppRoute.QUESTION_CATEGORY,
@ -123,11 +169,11 @@ fun AppNavigation(
) {
QuestionCategoryScreen(
categoryId = it.arguments?.getString("categoryId") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
composable(route = AppRoute.QUESTION_COMPOSER) {
QuestionComposerScreen(onNavigate = navController::navigate)
QuestionComposerScreen(onNavigate = navigateRoute)
}
// Question Thread
@ -153,7 +199,7 @@ fun AppNavigation(
questionId = it.arguments?.getString("questionId") ?: "",
previousQuestionId = it.arguments?.getString("prevId"),
nextQuestionId = it.arguments?.getString("nextId"),
onNavigate = navController::navigate,
onNavigate = navigateRoute,
onBack = { navController.popBackStack() }
)
}
@ -165,25 +211,25 @@ fun AppNavigation(
) {
AnswerRevealScreen(
questionId = it.arguments?.getString("questionId") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
composable(route = AppRoute.ANSWER_HISTORY) {
AnswerHistoryScreen(onNavigate = navController::navigate)
AnswerHistoryScreen(onNavigate = navigateRoute)
}
// Pairing
composable(route = AppRoute.CREATE_INVITE) {
CreateInviteScreen(
onNavigate = navController::navigate,
onNavigate = navigateRoute,
onBack = { navController.popBackStack() }
)
}
composable(route = AppRoute.EMAIL_INVITE) {
EmailInviteScreen(onNavigate = navController::navigate)
EmailInviteScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.ACCEPT_INVITE) {
AcceptInviteScreen(onNavigate = navController::navigate)
AcceptInviteScreen(onNavigate = navigateRoute)
}
composable(
route = AppRoute.INVITE_CONFIRM,
@ -191,13 +237,13 @@ fun AppNavigation(
) {
InviteConfirmScreen(
inviteCode = it.arguments?.getString("inviteCode") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
// Wheel / Category Selection
composable(route = AppRoute.CATEGORY_PICKER) {
CategoryPickerScreen(onNavigate = navController::navigate)
CategoryPickerScreen(onNavigate = navigateRoute)
}
composable(
route = AppRoute.SPIN_WHEEL,
@ -205,7 +251,7 @@ fun AppNavigation(
) {
SpinWheelScreen(
categoryId = it.arguments?.getString("categoryId") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
composable(
@ -214,7 +260,7 @@ fun AppNavigation(
) {
WheelSessionScreen(
sessionId = it.arguments?.getString("sessionId") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
composable(
@ -223,39 +269,39 @@ fun AppNavigation(
) {
WheelCompleteScreen(
sessionId = it.arguments?.getString("sessionId") ?: "",
onNavigate = navController::navigate
onNavigate = navigateRoute
)
}
composable(route = AppRoute.WHEEL_HISTORY) {
WheelHistoryScreen(onNavigate = navController::navigate)
WheelHistoryScreen(onNavigate = navigateRoute)
}
// Paywall
composable(route = AppRoute.PAYWALL) {
PaywallScreen(onNavigate = navController::navigate)
PaywallScreen(onNavigate = navigateRoute)
}
// Settings
composable(route = AppRoute.SETTINGS) {
SettingsScreen(onNavigate = navController::navigate)
SettingsScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.ACCOUNT) {
AccountScreen(onNavigate = navController::navigate)
AccountScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.NOTIFICATIONS) {
NotificationSettingsScreen(onNavigate = navController::navigate)
NotificationSettingsScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.PRIVACY) {
PrivacyScreen(onNavigate = navController::navigate)
PrivacyScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.SUBSCRIPTION) {
SubscriptionScreen(onNavigate = navController::navigate)
SubscriptionScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.RELATIONSHIP_SETTINGS) {
RelationshipSettingsScreen(onNavigate = navController::navigate)
RelationshipSettingsScreen(onNavigate = navigateRoute)
}
composable(route = AppRoute.DELETE_ACCOUNT) {
DeleteAccountScreen(onNavigate = navController::navigate)
DeleteAccountScreen(onNavigate = navigateRoute)
}
}
}
@ -271,10 +317,25 @@ private val topLevelRoutes = listOf(
TopLevelRoute(AppRoute.HOME, "Home", Icons.Filled.Home),
TopLevelRoute(AppRoute.DAILY_QUESTION, "Today", Icons.Filled.Favorite),
TopLevelRoute(AppRoute.QUESTION_PACKS, "Packs", Icons.Filled.Star),
TopLevelRoute(AppRoute.ANSWER_HISTORY, "Answers", Icons.Filled.Favorite),
TopLevelRoute(AppRoute.ANSWER_HISTORY, "Answers", Icons.Filled.Done),
TopLevelRoute(AppRoute.SETTINGS, "Settings", Icons.Filled.Settings)
)
private val shellBackRoutes = setOf(
AppRoute.PARTNER_HOME,
AppRoute.QUESTION_CATEGORY,
AppRoute.QUESTION_COMPOSER,
AppRoute.QUESTION_THREAD,
AppRoute.ANSWER_REVEAL,
AppRoute.CATEGORY_PICKER,
AppRoute.SPIN_WHEEL,
AppRoute.WHEEL_SESSION,
AppRoute.WHEEL_COMPLETE,
AppRoute.ACCOUNT,
AppRoute.SUBSCRIPTION,
AppRoute.PAYWALL
)
@Composable
private fun AppBottomNavigation(
currentRoute: String?,

View File

@ -51,23 +51,23 @@ object AppRoute {
Definition(SIGN_UP, "Sign Up", "auth"),
Definition(FORGOT_PASSWORD, "Forgot Password", "auth"),
Definition(HOME, "Home", "home"),
Definition(PARTNER_HOME, "Partner Home", "home"),
Definition(PARTNER_HOME, "Partner", "home"),
Definition(DAILY_QUESTION, "Daily Question", "questions"),
Definition(QUESTION_PACKS, "Question Packs", "questions"),
Definition(QUESTION_CATEGORY, "Question Category", "questions"),
Definition(QUESTION_COMPOSER, "Question Composer", "questions"),
Definition(QUESTION_THREAD, "Question Thread", "questions"),
Definition(ANSWER_REVEAL, "Answer Reveal", "answers"),
Definition(QUESTION_CATEGORY, "Question Pack", "questions"),
Definition(QUESTION_COMPOSER, "New Question", "questions"),
Definition(QUESTION_THREAD, "Answer", "questions"),
Definition(ANSWER_REVEAL, "Reveal", "answers"),
Definition(ANSWER_HISTORY, "Answer History", "answers"),
Definition(CREATE_INVITE, "Create Invite", "pairing"),
Definition(EMAIL_INVITE, "Email Invite", "pairing"),
Definition(ACCEPT_INVITE, "Accept Invite", "pairing"),
Definition(INVITE_CONFIRM, "Invite Confirm", "pairing"),
Definition(CATEGORY_PICKER, "Category Picker", "wheel"),
Definition(SPIN_WHEEL, "Spin Wheel", "wheel"),
Definition(CATEGORY_PICKER, "Choose A Category", "wheel"),
Definition(SPIN_WHEEL, "Spin", "wheel"),
Definition(WHEEL_SESSION, "Wheel Session", "wheel"),
Definition(WHEEL_COMPLETE, "Wheel Complete", "wheel"),
Definition(PAYWALL, "Paywall", "paywall"),
Definition(WHEEL_COMPLETE, "Complete", "wheel"),
Definition(PAYWALL, "Unlock Everything", "paywall"),
Definition(SETTINGS, "Settings", "settings"),
Definition(ACCOUNT, "Account", "settings"),
Definition(NOTIFICATIONS, "Notifications", "settings"),
@ -78,6 +78,52 @@ object AppRoute {
Definition(WHEEL_HISTORY, "Wheel History", "wheel")
)
val topLevelRoutes = setOf(
HOME,
DAILY_QUESTION,
QUESTION_PACKS,
ANSWER_HISTORY,
SETTINGS
)
val onboardingAuthRoutes = setOf(
ONBOARDING,
CREATE_PROFILE,
LOGIN,
SIGN_UP,
FORGOT_PASSWORD
)
val modalLikeRoutes = setOf(
CREATE_INVITE,
EMAIL_INVITE,
ACCEPT_INVITE,
INVITE_CONFIRM,
PAYWALL
)
val drillInRoutes = setOf(
PARTNER_HOME,
QUESTION_CATEGORY,
QUESTION_COMPOSER,
QUESTION_THREAD,
ANSWER_REVEAL,
CATEGORY_PICKER,
SPIN_WHEEL,
WHEEL_SESSION,
WHEEL_COMPLETE,
WHEEL_HISTORY,
ACCOUNT,
NOTIFICATIONS,
PRIVACY,
SUBSCRIPTION,
RELATIONSHIP_SETTINGS,
DELETE_ACCOUNT
)
fun titleFor(route: String?): String? =
definitions.firstOrNull { it.route == route }?.title
fun answerReveal(questionId: String): String = "answer_reveal/${questionId.asRouteArg()}"
fun inviteConfirm(inviteCode: String): String = "invite_confirm/${inviteCode.asRouteArg()}"

View File

@ -106,7 +106,7 @@ private fun AnswerRevealContent(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
CircularProgressIndicator(color = Color(0xFFE07A5F))
CircularProgressIndicator(color = Color(0xFF8F5FC8))
Text("Loading reveal")
}
}
@ -166,7 +166,10 @@ private fun NoAnswerState(
onClick = onAnswerQuestion,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Answer")
}
@ -208,7 +211,10 @@ private fun ReadyToRevealState(
onClick = onReveal,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Reveal")
}
@ -247,7 +253,7 @@ private fun RevealedState(
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
color = Color(0xFFFFF5F1)
color = Color(0xFFF8F0FF)
) {
Text(
text = answer.revealSummary(),

View File

@ -50,7 +50,10 @@ fun EmptyState(
onClick = onAction,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text(actionLabel)
}

View File

@ -37,7 +37,7 @@ fun LoadingState(
) {
CircularProgressIndicator(
modifier = Modifier.size(34.dp),
color = Color(0xFFE07A5F)
color = Color(0xFF8F5FC8)
)
Text(
text = message,

View File

@ -54,7 +54,7 @@ fun PlaceholderScreen(
route: String,
onNavigate: (String) -> Unit,
modifier: Modifier = Modifier,
accent: Color = Color(0xFFE07A5F),
accent: Color = Color(0xFFB98AF4),
primaryAction: PlaceholderAction? = null,
secondaryAction: PlaceholderAction? = null,
chips: List<String> = emptyList(),
@ -128,7 +128,7 @@ fun PlaceholderScreen(
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = accent,
contentColor = Color.White
contentColor = Color(0xFF271236)
),
shape = RoundedCornerShape(16.dp)
) {

View File

@ -24,31 +24,44 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
private val Purple = Color(0xFF7C6F9E)
private val Purple = Color(0xFF5F3A87)
private val PurpleLight = Color(0xFFF0EDF9)
private val GreenPill = Color(0xFF81B29A)
@Composable
fun SpecialDatesSection(modifier: Modifier = Modifier) {
fun SpecialDatesSection(
modifier: Modifier = Modifier,
compact: Boolean = false
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
shape = RoundedCornerShape(if (compact) 24.dp else 28.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
elevation = CardDefaults.cardElevation(defaultElevation = if (compact) 4.dp else 8.dp)
) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
modifier = Modifier.padding(if (compact) 16.dp else 20.dp),
verticalArrangement = Arrangement.spacedBy(if (compact) 12.dp else 16.dp)
) {
Text(
text = "Your Special Dates",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color(0xFF27211F)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Your Special Dates",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color(0xFF27211F)
)
if (compact) {
DateCountPill()
}
}
// Anniversary featured card
Surface(
@ -57,11 +70,11 @@ fun SpecialDatesSection(modifier: Modifier = Modifier) {
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(16.dp),
modifier = Modifier.padding(if (compact) 13.dp else 16.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically
) {
DateBlock(day = "14", month = "Jul", tint = Purple)
DateBlock(day = "14", month = "Jul", tint = Purple, compact = compact)
Column(
modifier = Modifier.weight(1f),
@ -76,7 +89,9 @@ fun SpecialDatesSection(modifier: Modifier = Modifier) {
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold),
color = Color(0xFF27211F)
)
TodayPill()
if (!compact) {
TodayPill()
}
}
Text(
text = "Added by Jessica",
@ -94,9 +109,19 @@ fun SpecialDatesSection(modifier: Modifier = Modifier) {
}
}
// Birthday rows
BirthdayRow(name = "Jessica", day = "10", month = "May")
BirthdayRow(name = "Mark", day = "25", month = "Aug")
if (compact) {
Text(
text = "Next up: Jessica's birthday May 10, Mark's birthday Aug 25",
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF4E4642),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
} else {
// Birthday rows
BirthdayRow(name = "Jessica", day = "10", month = "May")
BirthdayRow(name = "Mark", day = "25", month = "Aug")
}
}
}
}
@ -174,6 +199,22 @@ private fun TodayPill() {
}
}
@Composable
private fun DateCountPill() {
Surface(
shape = RoundedCornerShape(999.dp),
color = PurpleLight
) {
Text(
text = "3 saved",
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
style = MaterialTheme.typography.labelSmall,
color = Purple,
fontWeight = FontWeight.SemiBold
)
}
}
@Preview(showBackground = true, backgroundColor = 0xFFFFFBFA)
@Composable
fun SpecialDatesSectionPreview() {

View File

@ -117,20 +117,18 @@ private fun HomeContent(
state.isLoading -> LoadingHomeCard()
state.error != null -> ErrorHomeCard(message = state.error, onRefresh = onRefresh)
else -> {
DailyQuestionCard(
TodayOverviewCard(
question = state.dailyQuestion,
stats = state.answerStats,
onDailyQuestion = onDailyQuestion,
onHistory = onHistory,
onPacks = onPacks
)
AnswerStatsRow(
stats = state.answerStats,
onHistory = onHistory
)
SpecialDatesSection(compact = true)
LatestAnswerCard(
latest = state.answerStats.latest,
onHistory = onHistory
)
SpecialDatesSection()
CategoryPreviewGrid(
categories = state.categories,
onCategory = onCategory,
@ -248,7 +246,7 @@ private fun PulsingInviteFab(
.size(56.dp)
.scale(ring1Scale)
.background(
color = Color(0xFFE07A5F).copy(alpha = ring1Alpha),
color = Color(0xFFB98AF4).copy(alpha = ring1Alpha),
shape = CircleShape
)
)
@ -258,7 +256,7 @@ private fun PulsingInviteFab(
.size(56.dp)
.scale(ring2Scale)
.background(
color = Color(0xFFE07A5F).copy(alpha = ring2Alpha),
color = Color(0xFFB98AF4).copy(alpha = ring2Alpha),
shape = CircleShape
)
)
@ -266,8 +264,8 @@ private fun PulsingInviteFab(
FloatingActionButton(
onClick = onClick,
modifier = Modifier.size(56.dp).scale(fabScale),
containerColor = Color(0xFFE07A5F),
contentColor = Color.White,
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236),
shape = CircleShape
) {
Icon(
@ -279,6 +277,149 @@ private fun PulsingInviteFab(
}
}
@Composable
private fun TodayOverviewCard(
question: Question?,
stats: HomeAnswerStats,
onDailyQuestion: () -> Unit,
onHistory: () -> Unit,
onPacks: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(30.dp),
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.9f)),
elevation = CardDefaults.cardElevation(defaultElevation = 14.dp)
) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
HomePill("Daily ritual")
question?.let { HomePill(it.category.displayCategoryName()) }
}
Text(
text = question?.text ?: "Your next question is ready.",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF27211F),
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(22.dp),
color = Color(0xFFF8F0FF)
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Next best action",
style = MaterialTheme.typography.labelLarge,
color = Color(0xFF6B4A86),
fontWeight = FontWeight.SemiBold
)
Text(
text = if (stats.private > 0) "${stats.private} private" else "${stats.total} saved",
style = MaterialTheme.typography.labelMedium,
color = Color(0xFF6B4A86)
)
}
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
OverviewMetric(
label = "Saved",
value = stats.total.toString(),
modifier = Modifier.weight(1f),
onClick = onHistory
)
OverviewMetric(
label = "Revealed",
value = stats.revealed.toString(),
modifier = Modifier.weight(1f),
onClick = onHistory
)
OverviewMetric(
label = "Private",
value = stats.private.toString(),
modifier = Modifier.weight(1f),
onClick = onHistory
)
}
}
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(
onClick = onDailyQuestion,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Answer")
}
OutlinedButton(
onClick = onPacks,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp)
) {
Text("Packs")
}
}
}
}
}
@Composable
private fun OverviewMetric(
label: String,
value: String,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
Card(
onClick = onClick,
modifier = modifier,
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.72f)),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
) {
Column(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
Text(
text = value,
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold),
color = Color(0xFF5F3A87),
maxLines = 1
)
Text(
text = label,
style = MaterialTheme.typography.labelMedium,
color = Color(0xFF4E4642),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
private fun DailyQuestionCard(
question: Question?,
@ -314,7 +455,10 @@ private fun DailyQuestionCard(
onClick = onDailyQuestion,
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Open")
}
@ -381,7 +525,7 @@ private fun StatCard(
Text(
text = value,
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color(0xFFE07A5F)
color = Color(0xFF5F3A87)
)
Text(
text = label,
@ -534,7 +678,7 @@ private fun LoadingHomeCard() {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
CircularProgressIndicator(color = Color(0xFFE07A5F))
CircularProgressIndicator(color = Color(0xFF8F5FC8))
Text(
text = "Opening the local dashboard",
style = MaterialTheme.typography.bodyMedium,
@ -572,7 +716,10 @@ private fun ErrorHomeCard(
Button(
onClick = onRefresh,
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Retry")
}

View File

@ -55,7 +55,7 @@ fun PaywallScreen(
.fillMaxSize()
.background(
Brush.linearGradient(
listOf(Color(0xFFFFFBFA), Color(0xFFF5EEE8), Color(0xFFEAF0F4)),
listOf(Color(0xFFFFFBFA), Color(0xFFF4ECFF), Color(0xFFEAF0F4)),
start = Offset.Zero,
end = Offset.Infinite
)
@ -112,7 +112,7 @@ fun PaywallScreen(
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = Color(0xFFE07A5F),
tint = Color(0xFF5F3A87),
modifier = Modifier.size(18.dp)
)
Text(
@ -128,7 +128,7 @@ fun PaywallScreen(
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFFE07A5F)),
colors = CardDefaults.cardColors(containerColor = Color(0xFFB98AF4)),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
) {
Column(
@ -139,12 +139,12 @@ fun PaywallScreen(
Text(
text = "Coming soon",
style = MaterialTheme.typography.labelLarge,
color = Color.White.copy(alpha = 0.8f)
color = Color(0xFF271236).copy(alpha = 0.74f)
)
Text(
text = "In-app purchase launching with the next build.",
style = MaterialTheme.typography.bodyMedium,
color = Color.White,
color = Color(0xFF271236),
textAlign = TextAlign.Center
)
}
@ -154,9 +154,12 @@ fun PaywallScreen(
onClick = { onNavigate("back") },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Subscribe (coming soon)", color = Color.White)
Text("Subscribe (coming soon)", color = Color(0xFF271236))
}
TextButton(onClick = { onNavigate("back") }) {

View File

@ -134,8 +134,8 @@ fun LocalQuestionContent(
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFE07A5F),
contentColor = Color.White
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text(

View File

@ -197,7 +197,7 @@ private fun CategoryLoadingCard() {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
CircularProgressIndicator(color = Color(0xFFE07A5F))
CircularProgressIndicator(color = Color(0xFF8F5FC8))
Text(
text = "Loading local prompts",
style = MaterialTheme.typography.bodyMedium,

View File

@ -129,8 +129,8 @@ private fun QuestionPackLibraryContent(
.padding(top = 6.dp, bottom = 22.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFE07A5F),
contentColor = Color.White
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Unlock all packs")
@ -236,7 +236,7 @@ private fun LoadingPackCard() {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
CircularProgressIndicator(color = Color(0xFFE07A5F))
CircularProgressIndicator(color = Color(0xFF8F5FC8))
Text(
text = "Loading local packs",
style = MaterialTheme.typography.bodyMedium,

View File

@ -209,7 +209,7 @@ private fun NotifToggleRow(
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(checkedThumbColor = Color(0xFFE07A5F), checkedTrackColor = Color(0xFFE07A5F).copy(alpha = 0.4f))
colors = SwitchDefaults.colors(checkedThumbColor = Color(0xFFB98AF4), checkedTrackColor = Color(0xFFB98AF4).copy(alpha = 0.4f))
)
}
}

View File

@ -88,6 +88,12 @@ fun SettingsScreen(
) {
// Profile card
Card(
onClick = {
onNavigate(
if (state.isPaired) AppRoute.RELATIONSHIP_SETTINGS
else AppRoute.CREATE_INVITE
)
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
@ -136,7 +142,7 @@ fun SettingsScreen(
if (state.isPaired) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = if (state.isPaired) Color(0xFFE07A5F) else MaterialTheme.colorScheme.onSurfaceVariant
tint = if (state.isPaired) Color(0xFF5F3A87) else MaterialTheme.colorScheme.onSurfaceVariant
)
Column(
modifier = Modifier.weight(1f),
@ -162,16 +168,12 @@ fun SettingsScreen(
)
}
}
if (!state.isPaired) {
Icon(
Icons.AutoMirrored.Filled.ArrowForwardIos,
contentDescription = null,
modifier = Modifier
.size(16.dp)
.clickable { onNavigate(AppRoute.CREATE_INVITE) },
tint = MaterialTheme.colorScheme.primary
)
}
Icon(
Icons.AutoMirrored.Filled.ArrowForwardIos,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary
)
}
}

View File

@ -17,7 +17,7 @@ fun SubscriptionScreen(
description = "A subscription management place for entitlement status, invoices, and plan changes.",
route = AppRoute.SUBSCRIPTION,
onNavigate = onNavigate,
accent = Color(0xFFE07A5F),
accent = Color(0xFFB98AF4),
primaryAction = PlaceholderAction("Paywall", AppRoute.PAYWALL),
secondaryAction = PlaceholderAction("Settings", AppRoute.SETTINGS),
chips = listOf("Entitlement", "Plan", "Restore"),

View File

@ -3,8 +3,8 @@ package com.couplesconnect.app.ui.theme
import androidx.compose.ui.graphics.Color
// Dark theme colors (for reference if needed)
val darkPrimaryColor = Color(0xFFE07A5F)
val darkPrimaryContainerColor = Color(0xFF5C3828)
val darkPrimaryColor = Color(0xFF8F5FC8)
val darkPrimaryContainerColor = Color(0xFF43255F)
val darkSecondaryColor = Color(0xFF81B29A)
val darkTertiaryColor = Color(0xFFF2CC8F)
val darkBackgroundColor = Color(0xFF1F1F1F)
@ -12,6 +12,7 @@ val darkSurfaceColor = Color(0xFF2D2D2D)
val darkErrorColor = Color(0xFFE76F51)
val darkOnPrimaryColor = Color(0xFFFFFFFF)
val darkOnPrimaryContainerColor = Color(0xFFF3E8FF)
val darkOnSecondaryColor = Color(0xFFFFFFFF)
val darkOnTertiaryColor = Color(0xFF3E3E3E)
val darkOnBackgroundColor = Color(0xFFE0E0E0)

View File

@ -21,9 +21,9 @@ fun CouplesConnectTheme(
)
}
// Warm color palette (Light theme)
val PrimaryColor = Color(0xFFE07A5F)
val PrimaryContainerColor = Color(0xFFF5E6D3)
// Purple-pink color palette (Light theme)
val PrimaryColor = Color(0xFF8F5FC8)
val PrimaryContainerColor = Color(0xFFF3E8FF)
val SecondaryColor = Color(0xFF81B29A)
val TertiaryColor = Color(0xFFF2CC8F)
val BackgroundColor = Color(0xFFFFFBFA)
@ -31,6 +31,7 @@ val SurfaceColor = Color(0xFFFFFBFA)
val ErrorColor = Color(0xFFE76F51)
val OnPrimaryColor = Color(0xFFFFFFFF)
val OnPrimaryContainerColor = Color(0xFF321545)
val OnSecondaryColor = Color(0xFFFFFFFF)
val OnTertiaryColor = Color(0xFF3E3E3E)
val OnBackgroundColor = Color(0xFF3E3E3E)
@ -41,6 +42,7 @@ val lightColors = lightColorScheme(
primary = PrimaryColor,
onPrimary = OnPrimaryColor,
primaryContainer = PrimaryContainerColor,
onPrimaryContainer = OnPrimaryContainerColor,
secondary = SecondaryColor,
onSecondary = OnSecondaryColor,
tertiary = TertiaryColor,
@ -57,6 +59,7 @@ val darkColors = darkColorScheme(
primary = PrimaryColor,
onPrimary = OnPrimaryColor,
primaryContainer = PrimaryContainerColor,
onPrimaryContainer = OnPrimaryContainerColor,
secondary = SecondaryColor,
onSecondary = OnSecondaryColor,
tertiary = TertiaryColor,

View File

@ -107,7 +107,7 @@ private fun CategoryPickerContent(
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(color = Color(0xFF7C6F9E))
CircularProgressIndicator(color = Color(0xFF5F3A87))
Text("Loading categories…", style = MaterialTheme.typography.bodyMedium, color = Color(0xFF4E4642))
}
}

View File

@ -143,7 +143,7 @@ private fun SpinWheelContent(
Text(
text = if (state.spunAndReady) "" else "",
fontSize = 64.sp,
color = if (state.spunAndReady) Color(0xFF81B29A) else Color(0xFF7C6F9E)
color = if (state.spunAndReady) Color(0xFF81B29A) else Color(0xFF5F3A87)
)
}
}
@ -173,7 +173,7 @@ private fun SpinWheelContent(
onClick = onStart,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF7C6F9E))
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5F3A87))
) {
Text("Start session", color = Color.White)
}
@ -185,7 +185,7 @@ private fun SpinWheelContent(
Text("Spin again")
}
}
state.isLoading -> CircularProgressIndicator(color = Color(0xFF7C6F9E))
state.isLoading -> CircularProgressIndicator(color = Color(0xFF5F3A87))
else -> {
Text(
text = "Tap to select ${SpinWheelViewModel.SESSION_SIZE} questions at random",
@ -198,7 +198,7 @@ private fun SpinWheelContent(
onClick = onSpin,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF7C6F9E))
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5F3A87))
) {
Text("Spin", color = Color.White)
}

View File

@ -203,9 +203,12 @@ private fun WheelHistoryLockedCard(onUnlock: () -> Unit) {
onClick = onUnlock,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFB98AF4),
contentColor = Color(0xFF271236)
)
) {
Text("Unlock premium", color = Color.White)
Text("Unlock premium", color = Color(0xFF271236))
}
}
}

View File

@ -120,7 +120,7 @@ private fun WheelSessionContent(
LinearProgressIndicator(
progress = { progress },
modifier = Modifier.fillMaxWidth(),
color = Color(0xFF7C6F9E),
color = Color(0xFF5F3A87),
trackColor = Color(0xFFE8E4F0)
)
@ -158,7 +158,7 @@ private fun WheelSessionContent(
onClick = onNext,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF7C6F9E))
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5F3A87))
) {
Text(
text = if (current + 1 >= total) "Finish" else "Next question",