package com.craigvg.lichun_android.ui.screens.retention import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.AttachMoney import androidx.compose.material.icons.filled.Flag import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Verified import androidx.compose.material.icons.filled.MyLocation import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.craigvg.lichun_android.domain.models.ActiveLifeGoal import com.craigvg.lichun_android.domain.models.CompletedLifeGoal import com.craigvg.lichun_android.ui.theme.AppColors import com.craigvg.lichun_android.ui.theme.AppSpacing import com.craigvg.lichun_android.ui.theme.AppTypography import com.craigvg.lichun_android.viewmodel.GameStateViewModel /** * Life Goals screen (Wave 2) — surfaces the player's forward-looking aspirations * from the cached `lifeGoalsUpdate` snapshot: active goals with progress bars + * rewards, the overall life score, and completed goals. `justCompleted` goals are * surfaced as a one-time celebratory snackbar. * * Android parity with iOS LifeGoalsView.swift (T012b). */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun LifeGoalsScreen( gameStateViewModel: GameStateViewModel = hiltViewModel(), onBack: () -> Unit = {} ) { val snapshot by gameStateViewModel.lifeGoals.collectAsStateWithLifecycle() val activeGoals = snapshot?.active ?: emptyList() val completedGoals = snapshot?.completed ?: emptyList() val lifeScore = snapshot?.lifeScore ?: 0 val justCompleted = snapshot?.justCompleted ?: emptyList() val snackbarHostState = remember { SnackbarHostState() } // Surface a one-time celebration for goals that completed this update tick. LaunchedEffect(justCompleted.map { it.id }) { val goal = justCompleted.firstOrNull() ?: return@LaunchedEffect val extra = if (justCompleted.size > 1) " (+${justCompleted.size - 1} more)" else "" snackbarHostState.showSnackbar("Goal achieved: ${goal.title}$extra") } Scaffold( topBar = { TopAppBar( title = { Text("Life Goals", style = AppTypography.headline) }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = AppColors.primaryText) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = AppColors.surfaceElevated) ) }, snackbarHost = { SnackbarHost(snackbarHostState) }, containerColor = AppColors.background ) { padding -> Column( modifier = Modifier .fillMaxSize() .padding(padding) .verticalScroll(rememberScrollState()) .padding(horizontal = AppSpacing.md) ) { Spacer(Modifier.height(AppSpacing.md)) LifeScoreHeader( score = lifeScore, activeCount = activeGoals.size, completedCount = completedGoals.size ) Spacer(Modifier.height(AppSpacing.lg)) if (activeGoals.isEmpty() && completedGoals.isEmpty()) { LifeGoalsEmptyState() } else { if (activeGoals.isNotEmpty()) { Text("Active Goals", style = AppTypography.headline, color = AppColors.primaryText) Spacer(Modifier.height(AppSpacing.sm)) activeGoals.forEach { goal -> ActiveLifeGoalCard(goal) Spacer(Modifier.height(AppSpacing.sm)) } Spacer(Modifier.height(AppSpacing.md)) } if (completedGoals.isNotEmpty()) { Text("Completed", style = AppTypography.headline, color = AppColors.primaryText) Spacer(Modifier.height(AppSpacing.sm)) completedGoals.forEach { goal -> CompletedLifeGoalCard(goal) Spacer(Modifier.height(AppSpacing.sm)) } } } Spacer(Modifier.height(AppSpacing.xl)) } } } @Composable private fun LifeScoreHeader(score: Int, activeCount: Int, completedCount: Int) { Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(AppSpacing.cornerRadius), colors = CardDefaults.cardColors(containerColor = AppColors.surfaceElevated) ) { Column( modifier = Modifier .fillMaxWidth() .padding(AppSpacing.md), horizontalAlignment = Alignment.CenterHorizontally ) { Box( modifier = Modifier .size(76.dp) .background(AppColors.prestige.copy(alpha = 0.18f), CircleShape), contentAlignment = Alignment.Center ) { Icon( Icons.Default.MyLocation, contentDescription = null, tint = AppColors.prestige, modifier = Modifier.size(36.dp) ) } Spacer(Modifier.height(AppSpacing.sm)) Text("$score", style = AppTypography.displayTitle, color = AppColors.primaryText) Text("Life Score", style = AppTypography.body, color = AppColors.secondaryText) Spacer(Modifier.height(AppSpacing.sm)) Row(horizontalArrangement = Arrangement.spacedBy(AppSpacing.lg)) { StatPill(activeCount, "Active", AppColors.intelligence) StatPill(completedCount, "Completed", AppColors.success) } } } } @Composable private fun StatPill(value: Int, label: String, color: androidx.compose.ui.graphics.Color) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("$value", style = AppTypography.headline, color = color) Text(label, style = AppTypography.caption, color = AppColors.secondaryText) } } @Composable private fun ActiveLifeGoalCard(goal: ActiveLifeGoal) { val fraction = (goal.progressPercent.coerceIn(0, 100)).toFloat() / 100f Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(AppSpacing.cornerRadius), colors = CardDefaults.cardColors(containerColor = AppColors.surfaceElevated), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column(modifier = Modifier.padding(AppSpacing.md)) { Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier .size(44.dp) .background(AppColors.prestige.copy(alpha = 0.15f), CircleShape), contentAlignment = Alignment.Center ) { Icon(Icons.Default.Flag, null, tint = AppColors.prestige, modifier = Modifier.size(20.dp)) } Spacer(Modifier.width(AppSpacing.sm)) Column(modifier = Modifier.weight(1f)) { Text(goal.title, style = AppTypography.bodyBold, color = AppColors.primaryText) if (goal.description.isNotEmpty()) { Text( goal.description, style = AppTypography.caption, color = AppColors.secondaryText, maxLines = 2 ) } } } Spacer(Modifier.height(AppSpacing.sm)) Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier .weight(1f) .height(8.dp) .clip(RoundedCornerShape(4.dp)) .background(AppColors.prestige.copy(alpha = 0.2f)) ) { Box( modifier = Modifier .fillMaxWidth(fraction) .fillMaxHeight() .clip(RoundedCornerShape(4.dp)) .background(AppColors.prestige) ) } Spacer(Modifier.width(8.dp)) Text("${goal.progressPercent}%", style = AppTypography.captionBold, color = AppColors.prestige) } if (goal.target > 0) { Spacer(Modifier.height(AppSpacing.xxs)) Text("${goal.current}/${goal.target}", style = AppTypography.caption, color = AppColors.secondaryText) } Spacer(Modifier.height(AppSpacing.xs)) Row(horizontalArrangement = Arrangement.spacedBy(AppSpacing.sm)) { if (goal.reward > 0) { RewardBadge(Icons.Default.AttachMoney, "$${goal.reward}", AppColors.money) } if (goal.lifeScore > 0) { RewardBadge(Icons.Default.Star, "+${goal.lifeScore} score", AppColors.prestige) } } } } } @Composable private fun RewardBadge(icon: androidx.compose.ui.graphics.vector.ImageVector, text: String, color: androidx.compose.ui.graphics.Color) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .clip(RoundedCornerShape(AppSpacing.smallCornerRadius)) .background(color.copy(alpha = 0.12f)) .padding(horizontal = AppSpacing.sm, vertical = 4.dp) ) { Icon(icon, null, tint = color, modifier = Modifier.size(12.dp)) Spacer(Modifier.width(4.dp)) Text(text, style = AppTypography.captionBold, color = color) } } @Composable private fun CompletedLifeGoalCard(goal: CompletedLifeGoal) { Card( modifier = Modifier .fillMaxWidth() .alpha(0.85f), shape = RoundedCornerShape(AppSpacing.cornerRadius), colors = CardDefaults.cardColors(containerColor = AppColors.success.copy(alpha = 0.1f)) ) { Row( modifier = Modifier .fillMaxWidth() .padding(AppSpacing.md), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(40.dp) .background(AppColors.success.copy(alpha = 0.15f), CircleShape), contentAlignment = Alignment.Center ) { Icon(Icons.Default.Verified, null, tint = AppColors.success, modifier = Modifier.size(20.dp)) } Spacer(Modifier.width(AppSpacing.sm)) Text(goal.title, style = AppTypography.bodyBold, color = AppColors.primaryText, modifier = Modifier.weight(1f)) } } } @Composable private fun LifeGoalsEmptyState() { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(AppSpacing.xl) ) { Icon( Icons.Default.MyLocation, null, tint = AppColors.disabledText, modifier = Modifier.size(56.dp) ) Spacer(Modifier.height(AppSpacing.md)) Text("No life goals yet", style = AppTypography.headline, color = AppColors.primaryText, textAlign = TextAlign.Center) Spacer(Modifier.height(AppSpacing.xs)) Text( "Keep living and your aspirations will appear here as you grow through each life stage.", style = AppTypography.body, color = AppColors.secondaryText, textAlign = TextAlign.Center ) } } }