UI 도 고치고... 이것저것 손댔다. AI 시켜서.

This commit is contained in:
2026-09-22 02:45:56 +09:00
parent df873d6765
commit 4de74a75e4
11 changed files with 903 additions and 862 deletions
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wordpuzzle.hangul" package="com.wordpuzzle.hangul"
android:versionCode="1" android:versionCode="2"
android:versionName="1.0.1" > android:versionName="1.0.1" >
<uses-sdk <uses-sdk
@@ -71,6 +71,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:testOnly="true"
android:theme="@style/Theme.WordPuzzle" > android:theme="@style/Theme.WordPuzzle" >
<!-- AdMob Sample App ID for testing --> <!-- AdMob Sample App ID for testing -->
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wordpuzzle.hangul" package="com.wordpuzzle.hangul"
android:versionCode="1" android:versionCode="2"
android:versionName="1.0.1" > android:versionName="1.0.1" >
<uses-sdk <uses-sdk
@@ -71,6 +71,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:testOnly="true"
android:theme="@style/Theme.WordPuzzle" > android:theme="@style/Theme.WordPuzzle" >
<!-- AdMob Sample App ID for testing --> <!-- AdMob Sample App ID for testing -->
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wordpuzzle.hangul" package="com.wordpuzzle.hangul"
android:versionCode="1" android:versionCode="2"
android:versionName="1.0.1" > android:versionName="1.0.1" >
<uses-sdk <uses-sdk
@@ -71,6 +71,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:testOnly="true"
android:theme="@style/Theme.WordPuzzle" > android:theme="@style/Theme.WordPuzzle" >
<!-- AdMob Sample App ID for testing --> <!-- AdMob Sample App ID for testing -->
@@ -1,35 +1,54 @@
package com.wordpuzzle.hangul.ui.game package com.wordpuzzle.hangul.ui.game
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
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 com.wordpuzzle.hangul.data.model.CrosswordCell
import com.wordpuzzle.hangul.data.model.CrosswordClue import com.wordpuzzle.hangul.data.model.CrosswordClue
import com.wordpuzzle.hangul.data.model.Direction import com.wordpuzzle.hangul.data.model.Direction
@Composable @Composable
fun ClueBar( fun ClueBar(
grid: List<List<CrosswordCell>>,
activeClue: CrosswordClue?, activeClue: CrosswordClue?,
clues: List<CrosswordClue>, clues: List<CrosswordClue>,
onSwitchDirection: () -> Unit, focusedRow: Int = -1,
focusedCol: Int = -1,
isExpandedMode: Boolean = false, isExpandedMode: Boolean = false,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val activePalette = getCluePalette(clues, activeClue) val clueFontSize = if (isExpandedMode) 13.5.sp else 12.sp
val barHeight = if (isExpandedMode) 68.dp else 50.dp val barHeight = if (isExpandedMode) 78.dp else 70.dp
val clueFontSize = if (isExpandedMode) 14.sp else 13.sp
val matchingClues = remember(focusedRow, focusedCol, clues, activeClue) {
if (focusedRow >= 0 && focusedCol >= 0) {
val found = clues.filter { isCellInClue(focusedRow, focusedCol, it) }
if (found.isNotEmpty()) {
found.sortedBy { it.direction }
} else if (activeClue != null) {
listOf(activeClue)
} else {
emptyList()
}
} else if (activeClue != null) {
listOf(activeClue)
} else {
emptyList()
}
}
val firstClue = matchingClues.getOrNull(0)
val secondClue = matchingClues.getOrNull(1)
Surface( Surface(
modifier = modifier modifier = modifier
@@ -37,100 +56,109 @@ fun ClueBar(
.height(barHeight) .height(barHeight)
.padding(horizontal = 10.dp, vertical = 2.dp), .padding(horizontal = 10.dp, vertical = 2.dp),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
color = Color(0xFFF1F5F9), color = Color.Transparent,
shadowElevation = 2.dp shadowElevation = 0.dp
) { ) {
Row( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 10.dp, vertical = 2.dp) .padding(horizontal = 10.dp, vertical = 6.dp)
.fillMaxWidth(), .fillMaxSize(),
verticalAlignment = Alignment.CenterVertically verticalArrangement = Arrangement.SpaceBetween
) { ) {
if (activeClue != null) { // 첫 번째 힌트 줄
Surface( if (firstClue != null) {
shape = RoundedCornerShape(4.dp), SingleClueItem(
color = activePalette.accent, clue = firstClue,
modifier = Modifier.padding(end = 6.dp) grid = grid,
) { clues = clues,
Text( clueFontSize = clueFontSize
text = if (activeClue.direction == Direction.ACROSS) "가로 ${activeClue.number}" else "세로 ${activeClue.number}",
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
fontSize = 11.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
AutoResizedText(
text = activeClue.clue,
targetFontSize = clueFontSize,
minFontSize = 8.5.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF263238),
modifier = Modifier.weight(1f),
maxLines = 2
) )
IconButton(
onClick = onSwitchDirection,
modifier = Modifier.size(28.dp)
) {
Icon(
imageVector = Icons.Default.SwapHoriz,
contentDescription = "가로/세로 전환",
tint = activePalette.accent,
modifier = Modifier.size(20.dp)
)
}
} else { } else {
Text( Text(
text = "퍼즐의 빈 칸을 터치해 단어를 시작하세요.", text = "퍼즐의 빈 칸을 터치해 단어를 시작하세요.",
fontSize = 12.sp, fontSize = 12.sp,
color = Color.Gray, color = Color.Gray,
modifier = Modifier.weight(1f) modifier = Modifier.fillMaxWidth()
) )
} }
// 두 번째 힌트 줄 (위치 고정을 위해 항상 영역 확보)
if (secondClue != null) {
SingleClueItem(
clue = secondClue,
grid = grid,
clues = clues,
clueFontSize = clueFontSize
)
} else {
Spacer(modifier = Modifier.fillMaxWidth().height(22.dp))
}
} }
} }
} }
@Composable @Composable
fun AutoResizedText( private fun SingleClueItem(
text: String, clue: CrosswordClue,
modifier: Modifier = Modifier, grid: List<List<CrosswordCell>>,
color: Color = Color.Unspecified, clues: List<CrosswordClue>,
fontWeight: FontWeight? = null, clueFontSize: TextUnit
minFontSize: TextUnit = 8.5.sp,
targetFontSize: TextUnit = 13.sp,
maxLines: Int = 2,
style: TextStyle = LocalTextStyle.current
) { ) {
var resizedFontSize by remember(text) { mutableStateOf(targetFontSize) } val isCompleted = isClueCompleted(grid, clue)
var shouldDraw by remember(text) { mutableStateOf(false) } val palette = getCluePalette(clues, clue)
// 폰트 크기에 비례하여 줄 간격을 촘촘하게 축소하여 2번째 줄 잘림 완벽 방지 val scrollState = rememberScrollState(0)
val computedLineHeight = (resizedFontSize.value * 1.18f).sp
Text( // 새로운 단어가 선택될 때마다 힌트 텍스트 스크롤 위치를 맨 앞으로 리셋
text = text, LaunchedEffect(clue.id) {
color = color, scrollState.scrollTo(0)
fontWeight = fontWeight, }
fontSize = resizedFontSize,
lineHeight = computedLineHeight, Row(
maxLines = maxLines, verticalAlignment = Alignment.CenterVertically,
overflow = TextOverflow.Clip, modifier = Modifier.fillMaxWidth()
onTextLayout = { result -> ) {
if (result.hasVisualOverflow && resizedFontSize > minFontSize) { Surface(
resizedFontSize = (resizedFontSize.value - 0.5f).coerceAtLeast(minFontSize.value).sp shape = RoundedCornerShape(4.dp),
} else { color = if (isCompleted) Color(0xFF388E3C) else palette.accent,
shouldDraw = true modifier = Modifier.padding(end = 6.dp)
} ) {
}, Text(
modifier = modifier.drawWithContent { text = if (clue.direction == Direction.ACROSS) {
if (shouldDraw) { if (isCompleted) "가로 ${clue.number} ✓" else "가로 ${clue.number}"
drawContent() } else {
} if (isCompleted) "세로 ${clue.number} ✓" else "세로 ${clue.number}"
}, },
style = style modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
) fontSize = 11.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
// 힌트 텍스트가 길 경우 2줄로 넘어가지 않고 가로로 부드럽게 스크롤되도록 처리
Box(
modifier = Modifier
.weight(1f)
.horizontalScroll(scrollState)
) {
Text(
text = clue.clue,
fontSize = clueFontSize,
fontWeight = FontWeight.Medium,
color = if (isCompleted) Color(0xFF1B5E20) else Color(0xFF263238),
maxLines = 1,
softWrap = false
)
}
}
}
private fun isCellInClue(r: Int, c: Int, clue: CrosswordClue?): Boolean {
if (clue == null) return false
return if (clue.direction == Direction.ACROSS) {
r == clue.startRow && c in clue.startCol until (clue.startCol + clue.length)
} else {
c == clue.startCol && r in clue.startRow until (clue.startRow + clue.length)
}
} }
@@ -45,6 +45,22 @@ fun getCluePalette(clues: List<CrosswordClue>, clue: CrosswordClue?): PastelPale
return PASTEL_PALETTES[index % PASTEL_PALETTES.size] return PASTEL_PALETTES[index % PASTEL_PALETTES.size]
} }
/**
* 특정 단어가 완성(해당 단어의 모든 셀에 정확한 글자가 입력)되었는지 검증합니다.
*/
fun isClueCompleted(grid: List<List<CrosswordCell>>, clue: CrosswordClue): Boolean {
for (i in 0 until clue.length) {
val r = if (clue.direction == Direction.ACROSS) clue.startRow else clue.startRow + i
val c = if (clue.direction == Direction.ACROSS) clue.startCol + i else clue.startCol
if (r !in grid.indices || c !in grid[r].indices) return false
val cell = grid[r][c]
if (cell.isBlank || cell.userChar == null || cell.userChar != cell.correctChar) {
return false
}
}
return true
}
@Composable @Composable
fun CrosswordGrid( fun CrosswordGrid(
grid: List<List<CrosswordCell>>, grid: List<List<CrosswordCell>>,
@@ -61,15 +77,15 @@ fun CrosswordGrid(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
// 절대 원칙: 스크린 가로 폭 전체에 100% 무조건 맞춰서 정사각형 퍼즐 보드 생성 // 스크린 가로 폭 전체에 무조건 맞춰서 정사각형 퍼즐 보드 생성
val fullWidth = maxWidth - 6.dp val fullWidth = maxWidth - 6.dp
val boardSize = fullWidth.coerceAtLeast(120.dp) val boardSize = fullWidth.coerceAtLeast(120.dp)
Surface( Surface(
modifier = Modifier.size(boardSize), modifier = Modifier.size(boardSize),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
color = Color(0xFFECEFF1), color = Color.Transparent,
shadowElevation = 3.dp shadowElevation = 0.dp
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -91,6 +107,7 @@ fun CrosswordGrid(
val isFocused = (r == focusedRow && c == focusedCol) val isFocused = (r == focusedRow && c == focusedCol)
val matchingClues = clues.filter { isCellInClue(r, c, it) } val matchingClues = clues.filter { isCellInClue(r, c, it) }
val isInActiveClue = isCellInClue(r, c, activeClue) val isInActiveClue = isCellInClue(r, c, activeClue)
val isCompletedWord = matchingClues.isNotEmpty() && matchingClues.any { isClueCompleted(grid, it) }
GridCellView( GridCellView(
cell = cell, cell = cell,
@@ -99,6 +116,7 @@ fun CrosswordGrid(
activeClue = activeClue, activeClue = activeClue,
isFocused = isFocused, isFocused = isFocused,
isInActiveClue = isInActiveClue, isInActiveClue = isInActiveClue,
isCompletedWord = isCompletedWord,
gridSize = gridSize, gridSize = gridSize,
onClick = { onCellClick(r, c) }, onClick = { onCellClick(r, c) },
modifier = Modifier modifier = Modifier
@@ -122,6 +140,7 @@ fun GridCellView(
activeClue: CrosswordClue?, activeClue: CrosswordClue?,
isFocused: Boolean, isFocused: Boolean,
isInActiveClue: Boolean, isInActiveClue: Boolean,
isCompletedWord: Boolean,
gridSize: Int, gridSize: Int,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
@@ -149,6 +168,7 @@ fun GridCellView(
val backgroundBrush = when { val backgroundBrush = when {
isFocused -> Brush.linearGradient(listOf(Color(0xFFFFE082), Color(0xFFFFD54F))) isFocused -> Brush.linearGradient(listOf(Color(0xFFFFE082), Color(0xFFFFD54F)))
isCompletedWord -> Brush.linearGradient(listOf(Color(0xFFC8E6C9), Color(0xFFA5D6A7))) // 완성된 단어: 초록색 배경
isIntersection -> Brush.linearGradient(listOf(primaryPalette.bg, secondaryPalette!!.bg)) isIntersection -> Brush.linearGradient(listOf(primaryPalette.bg, secondaryPalette!!.bg))
else -> Brush.linearGradient(listOf(primaryPalette.bg, primaryPalette.bg)) else -> Brush.linearGradient(listOf(primaryPalette.bg, primaryPalette.bg))
} }
@@ -161,11 +181,12 @@ fun GridCellView(
val borderColor = when { val borderColor = when {
isFocused -> PrimaryIndigo isFocused -> PrimaryIndigo
isCompletedWord -> Color(0xFF388E3C) // 완성된 단어: 진한 녹색 테두리
isInActiveClue -> activeBorderColor isInActiveClue -> activeBorderColor
else -> primaryPalette.border else -> primaryPalette.border
} }
val borderWidth = if (isFocused) 2.5.dp else if (isInActiveClue) 2.dp else 1.2.dp val borderWidth = if (isFocused) 2.5.dp else if (isCompletedWord || isInActiveClue) 2.dp else 1.2.dp
val charFontSize = when { val charFontSize = when {
gridSize <= 5 -> 22.sp gridSize <= 5 -> 22.sp
@@ -188,7 +209,11 @@ fun GridCellView(
text = clueNum.toString(), text = clueNum.toString(),
fontSize = badgeFontSize, fontSize = badgeFontSize,
fontWeight = FontWeight.ExtraBold, fontWeight = FontWeight.ExtraBold,
color = if (isFocused) PrimaryIndigoDark else Color(0xFF546E7A), color = when {
isFocused -> PrimaryIndigoDark
isCompletedWord -> Color(0xFF1B5E20)
else -> Color(0xFF546E7A)
},
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.padding(start = 2.dp, top = 1.dp) .padding(start = 2.dp, top = 1.dp)
@@ -201,6 +226,7 @@ fun GridCellView(
fontSize = charFontSize, fontSize = charFontSize,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = when { color = when {
isCompletedWord -> Color(0xFF1B5E20) // 완성된 단어 글자: 짙은 선명 녹색
cell.isRevealedByHint -> CorrectGreen cell.isRevealedByHint -> CorrectGreen
else -> TextPrimary else -> TextPrimary
} }
@@ -99,21 +99,12 @@ fun GameScreen(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
Column { Text(
Text( text = "스테이지 $stageNumber",
text = "스테이지 $stageNumber", fontSize = 17.sp,
fontSize = 17.sp, fontWeight = FontWeight.Bold,
fontWeight = FontWeight.Bold, color = Color(0xFF212121)
color = Color(0xFF212121) )
)
uiState.stage?.let {
Text(
text = it.title,
fontSize = 11.sp,
color = Color(0xFF757575)
)
}
}
}, },
navigationIcon = { navigationIcon = {
IconButton(onClick = onNavigateBack) { IconButton(onClick = onNavigateBack) {
@@ -128,16 +119,16 @@ fun GameScreen(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
// 1. 새로고침 (셔플) 버튼 - 힌트 버튼 왼편 배치 // 1. 새로고침 (셔플) 버튼
IconButton( IconButton(
onClick = { viewModel.onShuffleTiles() }, onClick = { viewModel.onShuffleTiles() },
modifier = Modifier.size(34.dp) modifier = Modifier.size(32.dp)
) { ) {
Icon( Icon(
imageVector = Icons.Default.Refresh, imageVector = Icons.Default.Refresh,
contentDescription = "문자 새로고침 및 재배치", contentDescription = "문자 새로고침 및 재배치",
tint = PrimaryIndigo, tint = PrimaryIndigo,
modifier = Modifier.size(22.dp) modifier = Modifier.size(20.dp)
) )
} }
@@ -148,15 +139,15 @@ fun GameScreen(
containerColor = Color(0xFFFFF3E0), containerColor = Color(0xFFFFF3E0),
contentColor = SecondaryOrange contentColor = SecondaryOrange
), ),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp), contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
modifier = Modifier.height(34.dp) modifier = Modifier.height(32.dp)
) { ) {
Icon( Icon(
imageVector = Icons.Default.Lightbulb, imageVector = Icons.Default.Lightbulb,
contentDescription = "힌트", contentDescription = "힌트",
modifier = Modifier.size(16.dp) modifier = Modifier.size(15.dp)
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(3.dp))
Text( Text(
text = "${uiState.hintCount}", text = "${uiState.hintCount}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -165,6 +156,7 @@ fun GameScreen(
} }
} }
}, },
windowInsets = TopAppBarDefaults.windowInsets,
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = BackgroundLight containerColor = BackgroundLight
) )
@@ -223,9 +215,11 @@ fun GameScreen(
Spacer(modifier = Modifier.height(if (isUltraLongRatio) 8.dp else 4.dp)) Spacer(modifier = Modifier.height(if (isUltraLongRatio) 8.dp else 4.dp))
ClueBar( ClueBar(
grid = stage.grid,
activeClue = uiState.activeClue, activeClue = uiState.activeClue,
clues = stage.clues, clues = stage.clues,
onSwitchDirection = { viewModel.onSwitchDirection() }, focusedRow = uiState.focusedRow,
focusedCol = uiState.focusedCol,
isExpandedMode = isUltraLongRatio isExpandedMode = isUltraLongRatio
) )
@@ -7,14 +7,11 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
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.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -22,7 +19,6 @@ import com.wordpuzzle.hangul.data.model.TileItem
import com.wordpuzzle.hangul.ui.theme.PrimaryIndigo import com.wordpuzzle.hangul.ui.theme.PrimaryIndigo
import com.wordpuzzle.hangul.ui.theme.PrimaryIndigoLight import com.wordpuzzle.hangul.ui.theme.PrimaryIndigoLight
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun TilePoolView( fun TilePoolView(
tiles: List<TileItem>, tiles: List<TileItem>,
@@ -38,8 +34,8 @@ fun TilePoolView(
vertical = if (isExpandedMode) 4.dp else 2.dp vertical = if (isExpandedMode) 4.dp else 2.dp
), ),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
color = Color.White, color = Color.Transparent,
shadowElevation = 4.dp shadowElevation = 0.dp
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -52,54 +48,42 @@ fun TilePoolView(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
) { ) {
// 하단 문자 패널 - 사용 가능한 타일만 노출 val columns = 6
val availableTiles = tiles.filter { !it.isUsed } val chunkedTiles = tiles.chunked(columns)
val tileSize = if (isExpandedMode) { val fontSize = if (isExpandedMode) 28.sp else 24.sp
when { val spacingDp = if (isExpandedMode) 6.dp else 5.dp
availableTiles.size > 16 -> 50.dp
availableTiles.size > 10 -> 58.dp
else -> 66.dp
}
} else {
when {
availableTiles.size > 16 -> 36.dp
availableTiles.size > 10 -> 40.dp
else -> 44.dp
}
}
val fontSize = if (isExpandedMode) { Column(
when { modifier = Modifier.fillMaxWidth(),
availableTiles.size > 16 -> 24.sp
availableTiles.size > 10 -> 28.sp
else -> 32.sp
}
} else {
when {
availableTiles.size > 16 -> 16.sp
availableTiles.size > 10 -> 18.sp
else -> 20.sp
}
}
val spacingDp = if (isExpandedMode) 8.dp else 5.dp
FlowRow(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = if (isExpandedMode) 4.dp else 2.dp),
horizontalArrangement = Arrangement.spacedBy(spacingDp, Alignment.CenterHorizontally),
verticalArrangement = Arrangement.spacedBy(spacingDp) verticalArrangement = Arrangement.spacedBy(spacingDp)
) { ) {
availableTiles.forEach { tile -> chunkedTiles.forEach { rowTiles ->
LetterTileButton( Row(
tile = tile, modifier = Modifier.fillMaxWidth(),
size = tileSize, horizontalArrangement = Arrangement.spacedBy(spacingDp)
fontSize = fontSize, ) {
onClick = { onTileClick(tile) } for (i in 0 until columns) {
) val tile = rowTiles.getOrNull(i)
if (tile != null && !tile.isUsed) {
LetterTileButton(
tile = tile,
fontSize = fontSize,
onClick = { onTileClick(tile) },
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
)
} else {
// 사용된 타일 및 빈 셀은 동일한 1:1 정사각형 고정 영역 유지
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
)
}
}
}
} }
} }
} }
@@ -109,13 +93,12 @@ fun TilePoolView(
@Composable @Composable
fun LetterTileButton( fun LetterTileButton(
tile: TileItem, tile: TileItem,
size: Dp = 40.dp, onClick: () -> Unit,
fontSize: TextUnit = 18.sp, modifier: Modifier = Modifier,
onClick: () -> Unit fontSize: TextUnit = 24.sp
) { ) {
Box( Box(
modifier = Modifier modifier = modifier
.size(size)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background(PrimaryIndigoLight) .background(PrimaryIndigoLight)
.border( .border(
@@ -216,7 +216,7 @@ fun MainLobbyScreen(
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 10.dp)
.clickable { .clickable {
coroutineScope.launch { coroutineScope.launch {
if (previewWordList.isNotEmpty()) { if (previewWordList.isNotEmpty()) {
@@ -241,11 +241,11 @@ fun MainLobbyScreen(
} }
} }
}, },
shape = RoundedCornerShape(14.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White), colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) elevation = CardDefaults.cardElevation(defaultElevation = 3.dp)
) { ) {
Column(modifier = Modifier.padding(12.dp)) { Column(modifier = Modifier.padding(16.dp)) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
@@ -254,37 +254,43 @@ fun MainLobbyScreen(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
text = "💡 퍼즐 단어 미리보기", text = "💡 퍼즐 단어 미리보기",
fontSize = 12.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = PrimaryIndigo color = PrimaryIndigo
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(6.dp))
Icon( Icon(
imageVector = Icons.Default.Refresh, imageVector = Icons.Default.Refresh,
contentDescription = "다른 단어 보기", contentDescription = "다른 단어 보기",
tint = PrimaryIndigo, tint = PrimaryIndigo,
modifier = Modifier.size(14.dp) modifier = Modifier.size(16.dp)
) )
} }
Text(
text = "터치하여 단어 변경 🔄",
fontSize = 11.5.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF757575)
)
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = previewWord!!.word, text = previewWord!!.word,
fontSize = 18.sp, fontSize = 22.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.ExtraBold,
color = Color(0xFF212121) color = Color(0xFF1A237E)
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(6.dp))
AutoVerticalScrollingText( AutoVerticalScrollingText(
text = previewWord!!.meaning, text = previewWord!!.meaning,
fontSize = 12.sp, fontSize = 14.5.sp,
lineHeight = 17.sp, lineHeight = 20.sp,
color = Color(0xFF616161), color = Color(0xFF424242),
height = 36.dp height = 48.dp
) )
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
[versions] [versions]
agp = "9.4.0" agp = "9.4.1"
kotlin = "2.2.10" kotlin = "2.2.10"
coreKtx = "1.15.0" coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
+2 -2
View File
@@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle. # Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the # For customization when using a Version Control System, please read the
# header note. # header note.
#Wed Sep 16 13:58:22 KST 2026 #Mon Sep 21 23:15:50 KST 2026
sdk.dir=/home/gerd/Workspace/Android/SDK sdk.dir=D\:\\Android\\SDK