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.Diamond import androidx.compose.material.icons.filled.EmojiEvents import androidx.compose.material.icons.filled.HelpOutline import androidx.compose.material.icons.filled.Lock 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.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.craigvg.lichun_android.domain.models.AchievementCategoryDisplay import com.craigvg.lichun_android.domain.models.AchievementSummary import com.craigvg.lichun_android.domain.models.CategorySummary import com.craigvg.lichun_android.domain.models.CollectionEntry 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 /** * Achievement Collection screen (Wave 2) — a trophy-case driven by the * per-category achievement `summary` (rides in `achievementsList`). Each category * section lists locked + unlocked achievements with progress + hint; secret * achievements show a "???" teaser until discovered; each category shows its * completion %. * * Android parity with iOS AchievementCollectionView.swift. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun AchievementCollectionScreen( gameStateViewModel: GameStateViewModel = hiltViewModel(), onBack: () -> Unit = {} ) { val summary by gameStateViewModel.achievementSummary.collectAsStateWithLifecycle() LaunchedEffect(Unit) { // Reuse the existing getAchievements command; the server includes the // collection summary alongside the flat list. gameStateViewModel.fetchAchievements() } Scaffold( topBar = { TopAppBar( title = { Text("Collection", style = AppTypography.headline) }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = AppColors.primaryText) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = AppColors.surfaceElevated) ) }, containerColor = AppColors.background ) { padding -> Column( modifier = Modifier .fillMaxSize() .padding(padding) .verticalScroll(rememberScrollState()) .padding(horizontal = AppSpacing.md) ) { Spacer(Modifier.height(AppSpacing.md)) val s = summary if (s == null) { CollectionLoadingState() } else { CollectionOverallHeader(s) Spacer(Modifier.height(AppSpacing.lg)) s.orderedCategories.forEach { (key, catSummary) -> CategorySection(key, catSummary) Spacer(Modifier.height(AppSpacing.md)) } } Spacer(Modifier.height(AppSpacing.xl)) } } } @Composable private fun CollectionOverallHeader(summary: AchievementSummary) { val fraction = (summary.progressPercent.coerceIn(0, 100)).toFloat() / 100f 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.EmojiEvents, null, tint = AppColors.prestige, modifier = Modifier.size(34.dp)) } Spacer(Modifier.height(AppSpacing.sm)) Text("${summary.unlocked} / ${summary.total}", style = AppTypography.title, color = AppColors.primaryText) Text("${summary.progressPercent}% collected", style = AppTypography.body, color = AppColors.secondaryText) Spacer(Modifier.height(AppSpacing.sm)) Box( modifier = Modifier .fillMaxWidth() .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) ) } } } } @Composable private fun CategorySection(categoryKey: String, summary: CategorySummary) { val color = AchievementCategoryDisplay.color(categoryKey) Column(verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)) { Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier .size(32.dp) .background(color.copy(alpha = 0.15f), CircleShape), contentAlignment = Alignment.Center ) { Icon(Icons.Default.EmojiEvents, null, tint = color, modifier = Modifier.size(16.dp)) } Spacer(Modifier.width(AppSpacing.sm)) Text( AchievementCategoryDisplay.title(categoryKey), style = AppTypography.headline, color = AppColors.primaryText, modifier = Modifier.weight(1f) ) Text( "${summary.unlocked}/${summary.total} • ${summary.progressPercent}%", style = AppTypography.captionBold, color = AppColors.secondaryText ) } Box( modifier = Modifier .fillMaxWidth() .height(6.dp) .clip(RoundedCornerShape(3.dp)) .background(color.copy(alpha = 0.2f)) ) { Box( modifier = Modifier .fillMaxWidth(summary.progressFraction) .fillMaxHeight() .clip(RoundedCornerShape(3.dp)) .background(color) ) } summary.entries.forEach { CollectionEntryRow(it) } } } @Composable private fun CollectionEntryRow(entry: CollectionEntry) { val isMystery = entry.hidden && !entry.unlocked Card( modifier = Modifier .fillMaxWidth() .alpha(if (entry.unlocked) 1f else 0.85f), shape = RoundedCornerShape(AppSpacing.cornerRadius), colors = CardDefaults.cardColors(containerColor = AppColors.surfaceElevated), elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) ) { Row( modifier = Modifier.padding(AppSpacing.md), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(44.dp) .background( (if (entry.unlocked) AppColors.success else AppColors.disabledText).copy(alpha = 0.15f), CircleShape ), contentAlignment = Alignment.Center ) { Icon( imageVector = when { isMystery -> Icons.Default.HelpOutline entry.unlocked -> Icons.Default.EmojiEvents else -> Icons.Default.Lock }, contentDescription = null, tint = if (entry.unlocked) AppColors.success else AppColors.disabledText, modifier = Modifier.size(20.dp) ) } Spacer(Modifier.width(AppSpacing.md)) Column(modifier = Modifier.weight(1f)) { Text(entry.displayName, style = AppTypography.bodyBold, color = AppColors.primaryText) val subtitle = entry.displaySubtitle.ifEmpty { if (isMystery) "Secret achievement" else "" } if (subtitle.isNotEmpty()) { Text(subtitle, style = AppTypography.caption, color = AppColors.secondaryText, maxLines = 2) } if (!entry.unlocked && entry.progressPercent in 1..99) { Spacer(Modifier.height(AppSpacing.xxs)) Box( modifier = Modifier .fillMaxWidth() .height(4.dp) .clip(RoundedCornerShape(2.dp)) .background(AppColors.disabledText.copy(alpha = 0.3f)) ) { Box( modifier = Modifier .fillMaxWidth(entry.progressFraction) .fillMaxHeight() .clip(RoundedCornerShape(2.dp)) .background(AppColors.prestige) ) } } } if (entry.reward > 0) { Spacer(Modifier.width(AppSpacing.sm)) Row(verticalAlignment = Alignment.CenterVertically) { Icon( Icons.Default.Diamond, null, tint = if (entry.unlocked) AppColors.diamond else AppColors.disabledText, modifier = Modifier.size(12.dp) ) Spacer(Modifier.width(2.dp)) Text( "${entry.reward}", style = AppTypography.captionBold, color = if (entry.unlocked) AppColors.diamond else AppColors.disabledText ) } } } } } @Composable private fun CollectionLoadingState() { Box(modifier = Modifier.fillMaxWidth().height(220.dp), contentAlignment = Alignment.Center) { Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator(color = AppColors.primary) Spacer(Modifier.height(AppSpacing.md)) Text("Loading your collection...", style = AppTypography.body, color = AppColors.secondaryText) } } }