mirror of
https://github.com/tiennm99/loto.git
synced 2026-09-02 14:20:46 +00:00
feat: section "Chờ" ring on label with pulse animation
- Add sectionHasWaiting state to track which sections have rows one cell
from bingo (in Chờ state).
- Render amber ring + 1.5s pulse on section labels when section has waiting.
- Skip animation if accessibility motion is disabled.
- Add BrandAmberLight color for the ring.
Upstream: a60ea08
This commit is contained in:
@@ -18,4 +18,10 @@ data class PlayerBoardUiState(
|
||||
val celebratedRows: Set<Int> = emptySet(),
|
||||
val notifiedWaitingRows: Set<Int> = 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<Boolean> = listOf(false, false, false),
|
||||
)
|
||||
|
||||
@@ -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<List<Int>>,
|
||||
crossed: List<List<Boolean>>,
|
||||
rowComplete: List<Boolean>,
|
||||
sectionHasWaiting: List<Boolean>,
|
||||
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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user