diff --git a/android/app/src/main/java/com/miti99/loto/state/PlayerBoardUiState.kt b/android/app/src/main/java/com/miti99/loto/state/PlayerBoardUiState.kt index fe0456e..6ad29dc 100644 --- a/android/app/src/main/java/com/miti99/loto/state/PlayerBoardUiState.kt +++ b/android/app/src/main/java/com/miti99/loto/state/PlayerBoardUiState.kt @@ -18,4 +18,10 @@ data class PlayerBoardUiState( val celebratedRows: Set = emptySet(), val notifiedWaitingRows: Set = emptySet(), val lastConsumedEventId: Long = 0L, + /** + * Per-section flag (size 3): rows 0-2, 3-5, 6-8. True iff any row in + * that section is one cell from bingo and not yet complete. Drives + * the persistent amber ring on the section label band. + */ + val sectionHasWaiting: List = listOf(false, false, false), ) diff --git a/android/app/src/main/java/com/miti99/loto/ui/board/PlayerBoardGrid.kt b/android/app/src/main/java/com/miti99/loto/ui/board/PlayerBoardGrid.kt index 2ae1b2d..1b1b445 100644 --- a/android/app/src/main/java/com/miti99/loto/ui/board/PlayerBoardGrid.kt +++ b/android/app/src/main/java/com/miti99/loto/ui/board/PlayerBoardGrid.kt @@ -1,14 +1,25 @@ package com.miti99.loto.ui.board +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -16,6 +27,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.miti99.loto.R +import com.miti99.loto.ui.theme.BrandAmberLight private val SECTION_STARTS = listOf(0, 3, 6) private val SECTION_LABEL_RES = listOf( @@ -38,20 +50,62 @@ fun PlayerBoardGrid( grid: List>, crossed: List>, rowComplete: List, + sectionHasWaiting: List, emptyCellColor: Color, onCellClick: (row: Int, col: Int) -> Unit, modifier: Modifier = Modifier, ) { + // Single shared pulse drives every section's amber ring so they + // animate in lockstep instead of N independent timers. + val pulse = rememberInfiniteTransition(label = "cho-pulse") + val pulseAlpha by pulse.animateFloat( + initialValue = 0.55f, + targetValue = 1.0f, + animationSpec = infiniteRepeatable( + tween(durationMillis = 1500, easing = LinearEasing), + repeatMode = RepeatMode.Reverse, + ), + label = "cho-alpha", + ) + Column(modifier = modifier.fillMaxWidth()) { SECTION_STARTS.forEachIndexed { sectionIdx, startRow -> - // Section label - Text( - text = stringResource(SECTION_LABEL_RES[sectionIdx]), - fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(start = 4.dp, top = if (sectionIdx > 0) 12.dp else 4.dp, bottom = 2.dp), - ) + val isWaiting = sectionHasWaiting.getOrNull(sectionIdx) ?: false + val labelColor = + if (isWaiting) BrandAmberLight + else MaterialTheme.colorScheme.onSurfaceVariant + + // Section label — amber ring + pulse when any of its 3 rows + // is one cell from bingo. + Box( + modifier = Modifier + .padding( + start = 4.dp, + top = if (sectionIdx > 0) 12.dp else 4.dp, + bottom = 2.dp, + ) + .then( + if (isWaiting) Modifier + .background( + color = BrandAmberLight.copy(alpha = pulseAlpha * 0.18f), + shape = RoundedCornerShape(8.dp), + ) + .border( + width = 1.dp, + color = BrandAmberLight.copy(alpha = pulseAlpha), + shape = RoundedCornerShape(8.dp), + ) + .padding(horizontal = 8.dp, vertical = 2.dp) + else Modifier + ), + ) { + Text( + text = stringResource(SECTION_LABEL_RES[sectionIdx]), + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = labelColor, + ) + } // 3-row sub-grid Column { diff --git a/android/app/src/main/java/com/miti99/loto/ui/theme/Color.kt b/android/app/src/main/java/com/miti99/loto/ui/theme/Color.kt index dc58e2c..73cd3f7 100644 --- a/android/app/src/main/java/com/miti99/loto/ui/theme/Color.kt +++ b/android/app/src/main/java/com/miti99/loto/ui/theme/Color.kt @@ -18,9 +18,9 @@ val BrandAmberDark = Color(0xFFFBBF24) // amber-400 val BrandEmeraldLight = Color(0xFF10B981) // emerald-500 val BrandEmeraldDark = Color(0xFF34D399) // emerald-400 -// Pink — called numbers ≤ 49, modal subtitle. -val BrandPinkLight = Color(0xFFEC4899) // pink-500 -val BrandPinkDark = Color(0xFFF472B6) // pink-400 +// Sky — called numbers ≤ 49 (single-accent palette, replaces former pink). +val BrandSky600 = Color(0xFF0284C7) // sky-600 (light theme) +val BrandSky400 = Color(0xFF38BDF8) // sky-400 (dark theme) // Purple — secondary brand (legacy Excel purple — also default empty cell). val BrandPurpleLight = Color(0xFF8B5CF6) // violet-500 @@ -30,9 +30,14 @@ val BrandPurpleDark = Color(0xFFA78BFA) // violet-400 val BrandRedLight = Color(0xFFEF4444) // red-500 val BrandRedDark = Color(0xFFF87171) // red-400 -// Indigo — player accent (settings highlight, "Tạo bảng mới" gradient). -val BrandIndigoLight = Color(0xFF6366F1) // indigo-500 -val BrandIndigoDark = Color(0xFF818CF8) // indigo-400 +// Deeper -600 shades for the solid-color (no-gradient) palette. +val BrandRose600 = Color(0xFFE11D48) // rose-600 +val BrandAmber600 = Color(0xFFD97706) // amber-600 +val BrandEmerald600 = Color(0xFF059669) // emerald-600 + +// Warm cream light background, replacing pure white. Matches upstream +// `--background: #fffbeb` so the page feels festive without a radial glow. +val BackgroundCream = Color(0xFFFFFBEB) // Empty cell legacy default (Excel "Standard Color: Purple"). User-overridable // via settings; mirrors web app's `--empty-cell-bg` CSS variable.