feat: 弃牌堆/摸牌堆1.8倍放大,手牌区高度可调0~240dp默认160dp

This commit is contained in:
flykhan 2026-04-26 22:19:28 +08:00
parent 570d96c5c0
commit 807976ca51
7 changed files with 124 additions and 40 deletions

View File

@ -70,6 +70,8 @@ fun UnoApp() {
var cardTheme by remember { mutableStateOf(CardTheme.load(context)) } var cardTheme by remember { mutableStateOf(CardTheme.load(context)) }
var tableBg by remember { mutableStateOf(TableBg.load(context)) } var tableBg by remember { mutableStateOf(TableBg.load(context)) }
var isLandscape by remember { mutableStateOf(prefs.getBoolean("landscape", false)) } var isLandscape by remember { mutableStateOf(prefs.getBoolean("landscape", false)) }
var cardScale by remember { mutableStateOf(loadCardScale(context)) }
var handOffset by remember { mutableStateOf(loadHandOffset(context)) }
// Apply orientation on start // Apply orientation on start
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -192,7 +194,8 @@ fun UnoApp() {
CompositionLocalProvider( CompositionLocalProvider(
LocalCardTheme provides cardTheme, LocalCardTheme provides cardTheme,
LocalTableBg provides tableBg LocalTableBg provides tableBg,
LocalCardScale provides cardScale
) { ) {
val enterAnim: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = { val enterAnim: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = {
fadeIn(animationSpec = tween(300)) + slideInHorizontally(animationSpec = tween(350)) { it / 4 } fadeIn(animationSpec = tween(300)) + slideInHorizontally(animationSpec = tween(350)) { it / 4 }
@ -401,6 +404,7 @@ fun UnoApp() {
totalPlayers = totalPlayers, totalPlayers = totalPlayers,
humanPlayerName = humanPlayerName, humanPlayerName = humanPlayerName,
botNames = botNames, botNames = botNames,
handOffset = handOffset,
onBackToMenu = { onBackToMenu = {
navController.navigate(Screen.MainMenu.route) { navController.navigate(Screen.MainMenu.route) {
popUpTo(0) { inclusive = true } popUpTo(0) { inclusive = true }
@ -423,6 +427,7 @@ fun UnoApp() {
currentTheme = cardTheme, currentTheme = cardTheme,
currentBg = tableBg, currentBg = tableBg,
isLandscape = isLandscape, isLandscape = isLandscape,
handOffset = handOffset,
onNameChanged = { name -> onNameChanged = { name ->
savedName = name savedName = name
prefs.edit().putString("player_name", name).apply() prefs.edit().putString("player_name", name).apply()
@ -435,6 +440,10 @@ fun UnoApp() {
tableBg = bg tableBg = bg
TableBg.save(context, bg) TableBg.save(context, bg)
}, },
onSetHandOffset = { offset ->
handOffset = offset
saveHandOffset(context, offset)
},
onToggleOrientation = { onToggleOrientation = {
isLandscape = !isLandscape isLandscape = !isLandscape
prefs.edit().putBoolean("landscape", isLandscape).apply() prefs.edit().putBoolean("landscape", isLandscape).apply()

View File

@ -16,6 +16,7 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
@ -32,21 +33,25 @@ fun CardView(
selected: Boolean = false, selected: Boolean = false,
playable: Boolean = true, playable: Boolean = true,
theme: CardTheme = LocalCardTheme.current, theme: CardTheme = LocalCardTheme.current,
scale: Float = LocalCardScale.current,
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
val cardBg = getCardBgColor(card.color.name) val cardBg = getCardBgColor(card.color.name)
val isWild = card.color == CardColor.WILD val isWild = card.color == CardColor.WILD
var showInfo by remember { mutableStateOf(false) } var showInfo by remember { mutableStateOf(false) }
val w = (60 * scale).dp
val h = (90 * scale).dp
val off = (-30 * scale).dp
Box { Box {
when (theme) { when (theme) {
CardTheme.CLASSIC -> ClassicCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.CLASSIC -> ClassicCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.ELEGANT -> ElegantCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.ELEGANT -> ElegantCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.MIDNIGHT -> MidnightCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.MIDNIGHT -> MidnightCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.NEON -> NeonCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.NEON -> NeonCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.PASTEL -> PastelCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.PASTEL -> PastelCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.FOREST -> ForestCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.FOREST -> ForestCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
CardTheme.OCEAN -> OceanCard(card, cardBg, isWild, modifier, selected, playable, onClick, onLongPress = { showInfo = true }) CardTheme.OCEAN -> OceanCard(card, cardBg, isWild, w, h, off, modifier, selected, playable, onClick, onLongPress = { showInfo = true })
} }
if (showInfo) { if (showInfo) {
@ -59,13 +64,13 @@ fun CardView(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun ClassicCard( private fun ClassicCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 8.dp else 2.dp, RoundedCornerShape(10.dp)) .shadow(if (selected) 8.dp else 2.dp, RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background( .background(
@ -82,14 +87,14 @@ private fun ClassicCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun ElegantCard( private fun ElegantCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
val borderColor = if (isWild) GoldAccent else cardBg.copy(alpha = 0.5f) val borderColor = if (isWild) GoldAccent else cardBg.copy(alpha = 0.5f)
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 8.dp else 3.dp, RoundedCornerShape(14.dp)) .shadow(if (selected) 8.dp else 3.dp, RoundedCornerShape(14.dp))
.clip(RoundedCornerShape(14.dp)) .clip(RoundedCornerShape(14.dp))
.background(Color.White) .background(Color.White)
@ -122,14 +127,14 @@ private fun ElegantCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun MidnightCard( private fun MidnightCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
val glow = if (isWild) Color.Magenta.copy(alpha = 0.6f) else cardBg.copy(alpha = 0.6f) val glow = if (isWild) Color.Magenta.copy(alpha = 0.6f) else cardBg.copy(alpha = 0.6f)
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 12.dp else 4.dp, RoundedCornerShape(12.dp), ambientColor = glow, spotColor = glow) .shadow(if (selected) 12.dp else 4.dp, RoundedCornerShape(12.dp), ambientColor = glow, spotColor = glow)
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.background(DarkCard) .background(DarkCard)
@ -153,14 +158,14 @@ private fun MidnightCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun NeonCard( private fun NeonCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
val neonColor = if (isWild) Color.Magenta else cardBg val neonColor = if (isWild) Color.Magenta else cardBg
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 10.dp else 4.dp, RoundedCornerShape(8.dp), ambientColor = neonColor, spotColor = neonColor) .shadow(if (selected) 10.dp else 4.dp, RoundedCornerShape(8.dp), ambientColor = neonColor, spotColor = neonColor)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(Color(0xFF0A0A0A)) .background(Color(0xFF0A0A0A))
@ -182,13 +187,13 @@ private fun NeonCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun PastelCard( private fun PastelCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 6.dp else 2.dp, RoundedCornerShape(16.dp)) .shadow(if (selected) 6.dp else 2.dp, RoundedCornerShape(16.dp))
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background( .background(
@ -218,13 +223,13 @@ private fun PastelCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun ForestCard( private fun ForestCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 6.dp else 2.dp, RoundedCornerShape(6.dp)) .shadow(if (selected) 6.dp else 2.dp, RoundedCornerShape(6.dp))
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(6.dp))
.background( .background(
@ -243,13 +248,13 @@ private fun ForestCard(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun OceanCard( private fun OceanCard(
card: Card, cardBg: Color, isWild: Boolean, card: Card, cardBg: Color, isWild: Boolean, w: Dp, h: Dp, off: Dp,
modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit modifier: Modifier, selected: Boolean, playable: Boolean, onClick: () -> Unit, onLongPress: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.width(60.dp).height(90.dp) .width(w).height(h)
.then(if (selected) Modifier.offset(y = (-30).dp) else Modifier) .then(if (selected) Modifier.offset(y = off) else Modifier)
.shadow(if (selected) 6.dp else 3.dp, RoundedCornerShape(10.dp)) .shadow(if (selected) 6.dp else 3.dp, RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background( .background(
@ -351,11 +356,13 @@ private fun CardContent(card: Card, isWild: Boolean) {
} }
@Composable @Composable
fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.current) { fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.current, scale: Float = LocalCardScale.current) {
val w = (60 * scale).dp
val h = (90 * scale).dp
when (theme) { when (theme) {
CardTheme.CLASSIC -> { CardTheme.CLASSIC -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(2.dp, RoundedCornerShape(10.dp)) .shadow(2.dp, RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background(DarkCard) .background(DarkCard)
@ -371,7 +378,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.ELEGANT -> { CardTheme.ELEGANT -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(3.dp, RoundedCornerShape(14.dp)) .shadow(3.dp, RoundedCornerShape(14.dp))
.clip(RoundedCornerShape(14.dp)) .clip(RoundedCornerShape(14.dp))
.background(Color.White) .background(Color.White)
@ -386,7 +393,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.MIDNIGHT -> { CardTheme.MIDNIGHT -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(4.dp, RoundedCornerShape(12.dp), ambientColor = Color.Magenta.copy(alpha = 0.4f)) .shadow(4.dp, RoundedCornerShape(12.dp), ambientColor = Color.Magenta.copy(alpha = 0.4f))
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.background(DarkCard) .background(DarkCard)
@ -396,7 +403,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.NEON -> { CardTheme.NEON -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(4.dp, RoundedCornerShape(8.dp), ambientColor = Color.Cyan) .shadow(4.dp, RoundedCornerShape(8.dp), ambientColor = Color.Cyan)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(Color(0xFF0A0A0A)) .background(Color(0xFF0A0A0A))
@ -406,7 +413,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.PASTEL -> { CardTheme.PASTEL -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(2.dp, RoundedCornerShape(16.dp)) .shadow(2.dp, RoundedCornerShape(16.dp))
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background(Brush.verticalGradient(listOf(Color(0xFFFFF3E0), Color(0xFFFCE4EC)))) .background(Brush.verticalGradient(listOf(Color(0xFFFFF3E0), Color(0xFFFCE4EC))))
@ -416,7 +423,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.FOREST -> { CardTheme.FOREST -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(2.dp, RoundedCornerShape(6.dp)) .shadow(2.dp, RoundedCornerShape(6.dp))
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(6.dp))
.background(Brush.verticalGradient(listOf(Color(0xFF2E7D32), Color(0xFF1B5E20)))) .background(Brush.verticalGradient(listOf(Color(0xFF2E7D32), Color(0xFF1B5E20))))
@ -426,7 +433,7 @@ fun CardBack(modifier: Modifier = Modifier, theme: CardTheme = LocalCardTheme.cu
} }
CardTheme.OCEAN -> { CardTheme.OCEAN -> {
Box( Box(
modifier = modifier.width(60.dp).height(90.dp) modifier = modifier.width(w).height(h)
.shadow(3.dp, RoundedCornerShape(10.dp)) .shadow(3.dp, RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background(Brush.verticalGradient(listOf(Color(0xFF0277BD), Color(0xFF00BCD4)))) .background(Brush.verticalGradient(listOf(Color(0xFF0277BD), Color(0xFF00BCD4))))

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.unogame.model.Card import com.unogame.model.Card
import com.unogame.model.CardColor import com.unogame.model.CardColor
import com.unogame.ui.theme.LocalCardScale
@Composable @Composable
fun PlayerHand( fun PlayerHand(
@ -25,6 +26,7 @@ fun PlayerHand(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val cardScale = LocalCardScale.current
Column(modifier = modifier) { Column(modifier = modifier) {
Text( Text(
@ -45,6 +47,7 @@ fun PlayerHand(
card = card, card = card,
selected = index == selectedIndex, selected = index == selectedIndex,
playable = isPlayable, playable = isPlayable,
scale = cardScale,
onClick = { onCardClick(index) }, onClick = { onCardClick(index) },
modifier = Modifier.padding(horizontal = 2.dp) modifier = Modifier.padding(horizontal = 2.dp)
) )

View File

@ -37,6 +37,7 @@ fun GameScreen(
onShowLog: () -> Unit = {}, onShowLog: () -> Unit = {},
onPlaySeven: (Int) -> Unit = {}, onPlaySeven: (Int) -> Unit = {},
isSevenZeroMode: Boolean = false, isSevenZeroMode: Boolean = false,
handOffset: Float = 0f,
errorMessage: String errorMessage: String
) { ) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@ -44,6 +45,8 @@ fun GameScreen(
var showColorPicker by remember { mutableStateOf(false) } var showColorPicker by remember { mutableStateOf(false) }
var selectedAutoCard by remember { mutableIntStateOf(-1) } var selectedAutoCard by remember { mutableIntStateOf(-1) }
val flipped = gameState.flipped val flipped = gameState.flipped
val cardScale = LocalCardScale.current
val centerScale = 1.8f // 弃牌堆/摸牌堆较大的尺寸
val topCard = gameState.topCard val topCard = gameState.topCard
val currentPlayer = gameState.currentPlayer val currentPlayer = gameState.currentPlayer
@ -63,7 +66,7 @@ fun GameScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .verticalScroll(scrollState)
.padding(top = 24.dp, bottom = 100.dp) .padding(top = 16.dp, bottom = (100f - handOffset).coerceAtLeast(0f).dp)
) { ) {
// 出牌状态条最多2行 // 出牌状态条最多2行
Box(modifier = Modifier.fillMaxWidth().heightIn(min = 36.dp, max = 56.dp)) { Box(modifier = Modifier.fillMaxWidth().heightIn(min = 36.dp, max = 56.dp)) {
@ -144,7 +147,7 @@ fun GameScreen(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(4.dp))
} }
// Other players // Other players
@ -176,7 +179,7 @@ fun GameScreen(
} }
} }
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(6.dp))
// Center area: discard pile + draw pile // Center area: discard pile + draw pile
Row( Row(
@ -189,6 +192,7 @@ fun GameScreen(
// Draw pile // Draw pile
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
CardBack( CardBack(
scale = centerScale,
modifier = Modifier.clickable(enabled = isMyTurn) { modifier = Modifier.clickable(enabled = isMyTurn) {
onDrawCard() onDrawCard()
} }
@ -216,6 +220,7 @@ fun GameScreen(
color = if (gameState.currentWildColor != null && topCard.color == CardColor.WILD) color = if (gameState.currentWildColor != null && topCard.color == CardColor.WILD)
gameState.currentWildColor!! else displayTop.color gameState.currentWildColor!! else displayTop.color
), ),
scale = centerScale,
playable = false playable = false
) )
} }
@ -325,6 +330,7 @@ fun GameScreen(
}, },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.offset(y = (-handOffset).dp)
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.background(LocalTableBg.current.color.copy(alpha = 0.9f)) .background(LocalTableBg.current.color.copy(alpha = 0.9f))

View File

@ -121,6 +121,7 @@ fun LocalGameScreen(
humanPlayerName: String, humanPlayerName: String,
mode: GameMode, mode: GameMode,
botNames: List<String> = emptyList(), botNames: List<String> = emptyList(),
handOffset: Float = 0f,
onBackToMenu: () -> Unit onBackToMenu: () -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -600,6 +601,7 @@ fun LocalGameScreen(
myPlayerId = myPlayerId, myPlayerId = myPlayerId,
isMyTurn = isMyTurn, isMyTurn = isMyTurn,
errorMessage = errorMessage, errorMessage = errorMessage,
handOffset = handOffset,
isSevenZeroMode = mode == GameMode.SEVEN_ZERO, isSevenZeroMode = mode == GameMode.SEVEN_ZERO,
onPlayCard = { index -> executePlay(index, selectedWildColor) }, onPlayCard = { index -> executePlay(index, selectedWildColor) },
onPlaySeven = { index -> executePlay(index, null) }, onPlaySeven = { index -> executePlay(index, null) },

View File

@ -41,9 +41,11 @@ fun SettingsScreen(
currentTheme: CardTheme, currentTheme: CardTheme,
currentBg: TableBg, currentBg: TableBg,
isLandscape: Boolean, isLandscape: Boolean,
handOffset: Float,
onNameChanged: (String) -> Unit, onNameChanged: (String) -> Unit,
onSetTheme: (CardTheme) -> Unit, onSetTheme: (CardTheme) -> Unit,
onSetBg: (TableBg) -> Unit, onSetBg: (TableBg) -> Unit,
onSetHandOffset: (Float) -> Unit,
onToggleOrientation: () -> Unit, onToggleOrientation: () -> Unit,
onBack: () -> Unit onBack: () -> Unit
) { ) {
@ -237,6 +239,37 @@ fun SettingsScreen(
onClick = onToggleOrientation onClick = onToggleOrientation
) )
Spacer(modifier = Modifier.height(12.dp))
// 手牌区高度调整
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = DarkSurface)
) {
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.UnfoldLess, null, tint = Color.White.copy(alpha = 0.6f), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(12.dp))
Text("手牌区高度", color = Color.White, fontSize = 15.sp, modifier = Modifier.weight(1f))
Text("${handOffset.toInt()}dp", color = GoldAccent, fontSize = 14.sp)
}
Spacer(modifier = Modifier.height(8.dp))
Slider(
value = handOffset,
onValueChange = { onSetHandOffset(it) },
valueRange = 0f..240f,
steps = 23,
modifier = Modifier.fillMaxWidth(),
colors = SliderDefaults.colors(
thumbColor = GoldAccent,
activeTrackColor = GoldAccent
)
)
Text("贴底 ← → 抬高", color = Color.White.copy(alpha = 0.35f), fontSize = 11.sp)
}
}
Spacer(modifier = Modifier.height(28.dp)) Spacer(modifier = Modifier.height(28.dp))
// About // About

View File

@ -1,6 +1,30 @@
package com.unogame.ui.theme package com.unogame.ui.theme
import android.content.Context
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
val LocalCardTheme = compositionLocalOf { CardTheme.ELEGANT } val LocalCardTheme = compositionLocalOf { CardTheme.ELEGANT }
val LocalTableBg = compositionLocalOf { TableBg.GREEN } val LocalTableBg = compositionLocalOf { TableBg.GREEN }
val LocalCardScale = compositionLocalOf { 1.0f }
// 手牌缩放0.5 ~ 1.3,默认 1.0(原始大小)
fun loadCardScale(context: Context): Float {
return context.getSharedPreferences("unogame_prefs", Context.MODE_PRIVATE)
.getFloat("card_scale", 1.0f)
}
fun saveCardScale(context: Context, scale: Float) {
context.getSharedPreferences("unogame_prefs", Context.MODE_PRIVATE)
.edit().putFloat("card_scale", scale).apply()
}
// 手牌区高度偏移dp越大手牌越靠上。默认 160范围 0~240
fun loadHandOffset(context: Context): Float {
return context.getSharedPreferences("unogame_prefs", Context.MODE_PRIVATE)
.getFloat("hand_offset", 160f)
}
fun saveHandOffset(context: Context, offset: Float) {
context.getSharedPreferences("unogame_prefs", Context.MODE_PRIVATE)
.edit().putFloat("hand_offset", offset).apply()
}