Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .junie/guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Project Guidelines

## Project Structure
This is a Kotlin Multiplatform project with Compose Multiplatform that includes:
* `composeApp` - Shared Kotlin code with Compose UI
* `iosApp` - iOS application

## Building the Project
When building this project, Junie should use the following Gradle task:
```
:shared:compileDebugSources
```
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ Any updates to `boardStatus` or `boardGuesses` will trigger our SwiftUI UI to be
* StarWars (https://github.com/joreilly/StarWars)
* WordMasterKMP (https://github.com/joreilly/WordMasterKMP)
* Chip-8 (https://github.com/joreilly/chip-8)
* FirebaseAILogicKMPSample (https://github.com/joreilly/FirebaseAILogicKMPSample)
4 changes: 0 additions & 4 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
Expand All @@ -29,8 +30,17 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import dev.johnoreilly.wordmaster.shared.LetterStatus
import dev.johnoreilly.wordmaster.shared.WordMasterService
import dev.johnoreilly.wordmaster.androidApp.theme.WordMasterTheme
Expand All @@ -55,7 +65,7 @@ fun MainLayout() {
Scaffold(
topBar = { WordMasterTopAppBar("WordMaster KMP") }
) { innerPadding ->
WordMasterView(Modifier.padding(innerPadding))
WordMasterView(Modifier.padding(innerPadding).imePadding())
}
}

Expand All @@ -71,18 +81,20 @@ fun WordMasterView(padding: Modifier) {

val boardGuesses by wordMasterService.boardGuesses.collectAsState()
val boardStatus by wordMasterService.boardStatus.collectAsState()
val revealedAnswer by wordMasterService.revealedAnswer.collectAsState()
val lastGuessCorrect by wordMasterService.lastGuessCorrect.collectAsState()

val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }

Row(padding.fillMaxSize().padding(16.dp), horizontalArrangement = Center) {
Row(padding.fillMaxSize().padding(16.dp), horizontalArrangement = Center, verticalAlignment = Alignment.CenterVertically) {

Column(horizontalAlignment = Alignment.CenterHorizontally) {
for (guessAttempt in 0 until WordMasterService.MAX_NUMBER_OF_GUESSES) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Row(horizontalArrangement = Arrangement.Center) {
for (character in 0 until WordMasterService.NUMBER_LETTERS) {
Column(
Modifier.padding(4.dp).background(Color.White).border(1.dp, Color.Black),
Modifier.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {

Expand All @@ -100,14 +112,64 @@ fun WordMasterView(padding: Modifier) {
character,
it.uppercase()
)
focusManager.moveFocus(FocusDirection.Next)
if (it.isNotEmpty() && character < WordMasterService.NUMBER_LETTERS - 1) {
// Only move within the same row; don't advance to the next row until the guess is submitted
focusManager.moveFocus(FocusDirection.Next)
}
}
},
modifier = modifier,
modifier = modifier.onKeyEvent {
if (it.type == KeyEventType.KeyUp && (it.key == Key.Enter || it.key == Key.NumPadEnter)) {
if (guessAttempt == wordMasterService.currentGuessAttempt) {
var filled = true
for (c in 0 until WordMasterService.NUMBER_LETTERS) {
if (boardGuesses[guessAttempt][c].isEmpty()) { filled = false; break }
}
if (filled) {
wordMasterService.checkGuess()
// After submitting a guess, move focus to the next row's first cell
focusManager.moveFocus(FocusDirection.Next)
return@onKeyEvent true
}
}
}
false
}
.border(1.dp, Color.Black.copy(alpha = 0.6f), androidx.compose.foundation.shape.RoundedCornerShape(10.dp)),
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
if (guessAttempt == wordMasterService.currentGuessAttempt) {
var filled = true
for (c in 0 until WordMasterService.NUMBER_LETTERS) {
if (boardGuesses[guessAttempt][c].isEmpty()) { filled = false; break }
}
if (filled) {
wordMasterService.checkGuess()
// After submitting a guess, move focus to the next row's first cell
focusManager.moveFocus(FocusDirection.Next)
}
}
}
),
textStyle = TextStyle(fontSize = 14.sp, textAlign = TextAlign.Center),
shape = androidx.compose.foundation.shape.RoundedCornerShape(10.dp),
colors = TextFieldDefaults.colors(
focusedTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
unfocusedTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
disabledTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
cursorColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
focusedContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
unfocusedContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
disabledContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
errorIndicatorColor = Color.Transparent,
),
)

Expand All @@ -121,9 +183,28 @@ fun WordMasterView(padding: Modifier) {
}

Spacer(Modifier.height(16.dp))

if (revealedAnswer != null) {
Text(
text = "Answer: $revealedAnswer",
style = TextStyle(fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface)
)
Spacer(Modifier.height(12.dp))
}

Row(horizontalArrangement = Arrangement.Center) {
Button(onClick = {
wordMasterService.checkGuess()
// Only submit and advance focus if the current row is filled
val current = wordMasterService.currentGuessAttempt
var filled = true
for (c in 0 until WordMasterService.NUMBER_LETTERS) {
if (boardGuesses[current][c].isEmpty()) { filled = false; break }
}
if (filled) {
wordMasterService.checkGuess()
// Move focus to next row's first cell
focusManager.moveFocus(FocusDirection.Next)
}
}) {
Text("Guess")
}
Expand All @@ -135,6 +216,23 @@ fun WordMasterView(padding: Modifier) {
Text("New Game")
}
}

if (lastGuessCorrect) {
androidx.compose.material3.AlertDialog(
onDismissRequest = { /* keep dialog until OK pressed */ },
title = { Text("You win!") },
text = { Text("Great job guessing the word.") },
confirmButton = {
Button(onClick = {
wordMasterService.resetGame()
// Re-focus first cell after reset
focusRequester.requestFocus()
}) {
Text("OK")
}
}
)
}
}
}

Expand Down
Loading
Loading