From cdfcce357d1c55805c0a960b4952a949ece529b1 Mon Sep 17 00:00:00 2001 From: flykhan Date: Sun, 26 Apr 2026 21:26:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=87=BA=E7=89=8C=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E9=87=8D=E6=9E=84=EF=BC=8C=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E7=AE=AD=E5=A4=B4=E5=A4=96=E7=BD=AE=E3=80=81=E5=AE=BD=E5=BA=A6?= =?UTF-8?q?=E6=8B=89=E6=BB=A1=E3=80=81=E7=BA=AF=E6=96=87=E5=AD=97=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E6=89=A9=E9=AB=98=EF=BC=9B=E7=8A=B6=E6=80=81=E6=9D=A1?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E8=87=B32=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/unogame/ui/screens/GameScreen.kt | 8 +- .../com/unogame/ui/screens/LocalGameScreen.kt | 199 +++++++++++------- 2 files changed, 127 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/unogame/ui/screens/GameScreen.kt b/app/src/main/java/com/unogame/ui/screens/GameScreen.kt index 002ab54..eafd31f 100644 --- a/app/src/main/java/com/unogame/ui/screens/GameScreen.kt +++ b/app/src/main/java/com/unogame/ui/screens/GameScreen.kt @@ -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( diff --git a/app/src/main/java/com/unogame/ui/screens/LocalGameScreen.kt b/app/src/main/java/com/unogame/ui/screens/LocalGameScreen.kt index 77c9aae..7f58cf8 100644 --- a/app/src/main/java/com/unogame/ui/screens/LocalGameScreen.kt +++ b/app/src/main/java/com/unogame/ui/screens/LocalGameScreen.kt @@ -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