feat: 出牌记录弹窗重构,上下箭头外置、宽度拉满、纯文字区域扩高;状态条扩展至2行

This commit is contained in:
flykhan 2026-04-26 21:26:24 +08:00
parent 13a1e9432a
commit cdfcce357d
2 changed files with 127 additions and 80 deletions

View File

@ -65,8 +65,8 @@ fun GameScreen(
.verticalScroll(scrollState)
.padding(top = 24.dp, bottom = 100.dp)
) {
// Game message - fixed height area
Box(modifier = Modifier.fillMaxWidth().heightIn(min = 36.dp)) {
// 出牌状态条最多2行
Box(modifier = Modifier.fillMaxWidth().heightIn(min = 36.dp, max = 56.dp)) {
if (gameState.message.isNotEmpty()) {
Card(
modifier = Modifier
@ -83,8 +83,10 @@ fun GameScreen(
Text(
text = gameState.message,
color = GoldAccent,
fontSize = 14.sp,
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Icon(

View File

@ -1,9 +1,14 @@
package com.unogame.ui.screens
import android.content.Context
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@ -12,6 +17,8 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.SpanStyle
@ -371,91 +378,129 @@ fun LocalGameScreen(
val creamBg = Color(0xFFFFF8E1) // 护眼米白
val dimText = Color(0xFF555555) // 浅色底上的暗灰文字
// 关闭
// 列表状态放在外层供标题栏和内容区共享
val listState = rememberLazyListState()
val canScrollForward by remember { derivedStateOf { listState.canScrollForward } }
val canScrollBackward by remember { derivedStateOf { listState.canScrollBackward } }
AlertDialog(
modifier = Modifier.fillMaxWidth(0.98f),
onDismissRequest = { showLogDialog = false },
title = { Text("出牌记录", color = Color(0xFF333333), fontWeight = FontWeight.Bold) },
text = {
Column(
modifier = Modifier
.heightIn(min = 300.dp, max = 420.dp)
.verticalScroll(rememberScrollState())
) {
if (gameLog.isEmpty()) {
Text("暂无记录", color = dimText, fontSize = 13.sp)
}
gameLog.forEachIndexed { index, msg ->
val playerName = gameLogPlayers.getOrElse(index) { parsePlayerName(msg) }
val avatar = getBotAvatar(playerName)
val colorText = parseColorText(msg)
val cardColor = cardColorMap[colorText]
val isPlay = " 出了 " in msg
val arrowColor = parseArrowColor(msg)
val afterPlayer = msg.substringAfter(playerName)
// 用 AnnotatedString 构建整行,支持自动换行
val annotated = buildAnnotatedString {
// 序号
withStyle(SpanStyle(color = Color(0xFF999999), fontSize = 13.sp)) {
append("${gameLog.size - index}. ")
}
// 玩家名
withStyle(SpanStyle(color = avatar.color, fontWeight = FontWeight.Bold, fontSize = 13.sp)) {
append(playerName)
}
if (isPlay && (colorText.isNotEmpty() || arrowColor != null)) {
if (arrowColor != null) {
val prefix = afterPlayer.substringBefore(" 万能")
val cardBlock = afterPlayer.substringAfter(" 出了 ").substringBefore("")
val remain = afterPlayer.substringAfter(prefix + " 出了 " + cardBlock)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix)
append(" 出了 ")
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = arrowColor.second.copy(alpha = 0.9f))) {
append(cardBlock)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
} else {
val prefix = afterPlayer.substringBefore(colorText)
val cardId = colorText + afterPlayer.substringAfter(colorText).substringBefore("")
val remain = afterPlayer.substringAfter(prefix + cardId)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix)
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = (cardColor ?: Color.Gray).copy(alpha = 0.9f))) {
append(cardId)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
}
} else {
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(afterPlayer)
}
}
}
Text(annotated, modifier = Modifier.padding(vertical = 5.dp))
// 条目间加细分割线
if (index < gameLog.size - 1) {
Spacer(modifier = Modifier.height(1.dp))
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("出牌记录", color = Color(0xFF333333), fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
if (gameLog.size > 4 && canScrollBackward) {
var topPressed by remember { mutableStateOf(false) }
val topScale by animateFloatAsState(if (topPressed) 1.4f else 1f, spring())
if (topPressed) {
LaunchedEffect(Unit) { kotlinx.coroutines.delay(400); topPressed = false }
}
IconButton(
onClick = { topPressed = true; scope.launch { listState.animateScrollToItem(0) } },
modifier = Modifier.size(28.dp).scale(topScale)
) { Icon(Icons.Default.KeyboardArrowUp, "到顶部", tint = Color(0xFF555555)) }
} else {
Spacer(modifier = Modifier.size(28.dp)) // 占位保持标题居中
}
}
},
text = {
// 记录内容
if (gameLog.isEmpty()) {
Text("暂无记录", color = dimText, fontSize = 13.sp)
} else {
Box(
modifier = Modifier
.height(450.dp)
.fillMaxWidth()
) {
// 滚动内容(纯文字区域)
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(gameLog) { index, msg ->
val playerName = gameLogPlayers.getOrElse(index) { parsePlayerName(msg) }
val avatar = getBotAvatar(playerName)
val colorText = parseColorText(msg)
val cardColor = cardColorMap[colorText]
val isPlay = " 出了 " in msg
val arrowColor = parseArrowColor(msg)
val afterPlayer = msg.substringAfter(playerName)
val annotated = buildAnnotatedString {
withStyle(SpanStyle(color = Color(0xFF999999), fontSize = 13.sp)) {
append("${gameLog.size - index}. ")
}
withStyle(SpanStyle(color = avatar.color, fontWeight = FontWeight.Bold, fontSize = 13.sp)) {
append(playerName)
}
if (isPlay && (colorText.isNotEmpty() || arrowColor != null)) {
if (arrowColor != null) {
val prefix = afterPlayer.substringBefore(" 万能")
val cardBlock = afterPlayer.substringAfter(" 出了 ").substringBefore("")
val remain = afterPlayer.substringAfter(prefix + " 出了 " + cardBlock)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix); append(" 出了 ")
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = arrowColor.second.copy(alpha = 0.9f))) {
append(cardBlock)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
} else {
val prefix = afterPlayer.substringBefore(colorText)
val cardId = colorText + afterPlayer.substringAfter(colorText).substringBefore("")
val remain = afterPlayer.substringAfter(prefix + cardId)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix)
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = (cardColor ?: Color.Gray).copy(alpha = 0.9f))) {
append(cardId)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
}
} else {
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(afterPlayer)
}
}
}
Text(annotated, modifier = Modifier.padding(vertical = 5.dp))
}
}
} // Box 结束
}
},
confirmButton = {
TextButton(onClick = { showLogDialog = false }) {
Text("关闭", color = Color(0xFF333333))
Row(verticalAlignment = Alignment.CenterVertically) {
if (canScrollForward) {
var bottomPressed by remember { mutableStateOf(false) }
val bottomScale by animateFloatAsState(if (bottomPressed) 1.4f else 1f, spring())
if (bottomPressed) {
LaunchedEffect(Unit) { kotlinx.coroutines.delay(400); bottomPressed = false }
}
IconButton(
onClick = { bottomPressed = true; scope.launch { listState.animateScrollToItem(gameLog.size - 1) } },
modifier = Modifier.size(28.dp).scale(bottomScale)
) { Icon(Icons.Default.KeyboardArrowDown, "到底部", tint = Color(0xFF555555)) }
} else {
Spacer(modifier = Modifier.size(28.dp))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = { showLogDialog = false }) {
Text("关闭", color = Color(0xFF333333))
}
}
},
containerColor = creamBg