@@ -17,6 +17,7 @@ import androidx.compose.material3.Scaffold
17
17
import androidx.compose.material3.Text
18
18
import androidx.compose.material3.TextField
19
19
import androidx.compose.material3.TextFieldDefaults
20
+ import androidx.compose.material3.MaterialTheme
20
21
import androidx.compose.material3.TopAppBar
21
22
import androidx.compose.runtime.*
22
23
import androidx.compose.ui.Alignment
@@ -29,8 +30,17 @@ import androidx.compose.ui.platform.LocalContext
29
30
import androidx.compose.ui.platform.LocalFocusManager
30
31
import androidx.compose.ui.text.TextStyle
31
32
import androidx.compose.ui.text.style.TextAlign
33
+ import androidx.compose.ui.text.input.KeyboardCapitalization
34
+ import androidx.compose.ui.text.input.ImeAction
35
+ import androidx.compose.foundation.text.KeyboardOptions
36
+ import androidx.compose.foundation.text.KeyboardActions
32
37
import androidx.compose.ui.unit.dp
33
38
import androidx.compose.ui.unit.sp
39
+ import androidx.compose.ui.input.key.onKeyEvent
40
+ import androidx.compose.ui.input.key.Key
41
+ import androidx.compose.ui.input.key.KeyEventType
42
+ import androidx.compose.ui.input.key.key
43
+ import androidx.compose.ui.input.key.type
34
44
import dev.johnoreilly.wordmaster.shared.LetterStatus
35
45
import dev.johnoreilly.wordmaster.shared.WordMasterService
36
46
import dev.johnoreilly.wordmaster.androidApp.theme.WordMasterTheme
@@ -55,7 +65,7 @@ fun MainLayout() {
55
65
Scaffold (
56
66
topBar = { WordMasterTopAppBar (" WordMaster KMP" ) }
57
67
) { innerPadding ->
58
- WordMasterView (Modifier .padding(innerPadding))
68
+ WordMasterView (Modifier .padding(innerPadding).imePadding() )
59
69
}
60
70
}
61
71
@@ -71,18 +81,20 @@ fun WordMasterView(padding: Modifier) {
71
81
72
82
val boardGuesses by wordMasterService.boardGuesses.collectAsState()
73
83
val boardStatus by wordMasterService.boardStatus.collectAsState()
84
+ val revealedAnswer by wordMasterService.revealedAnswer.collectAsState()
85
+ val lastGuessCorrect by wordMasterService.lastGuessCorrect.collectAsState()
74
86
75
87
val focusManager = LocalFocusManager .current
76
88
val focusRequester = remember { FocusRequester () }
77
89
78
- Row (padding.fillMaxSize().padding(16 .dp), horizontalArrangement = Center ) {
90
+ Row (padding.fillMaxSize().padding(16 .dp), horizontalArrangement = Center , verticalAlignment = Alignment . CenterVertically ) {
79
91
80
92
Column (horizontalAlignment = Alignment .CenterHorizontally ) {
81
93
for (guessAttempt in 0 until WordMasterService .MAX_NUMBER_OF_GUESSES ) {
82
- Row (horizontalArrangement = Arrangement .SpaceBetween ) {
94
+ Row (horizontalArrangement = Arrangement .Center ) {
83
95
for (character in 0 until WordMasterService .NUMBER_LETTERS ) {
84
96
Column (
85
- Modifier .padding(4 .dp).background( Color . White ).border( 1 .dp, Color . Black ) ,
97
+ Modifier .padding(4 .dp),
86
98
horizontalAlignment = Alignment .CenterHorizontally
87
99
) {
88
100
@@ -100,14 +112,64 @@ fun WordMasterView(padding: Modifier) {
100
112
character,
101
113
it.uppercase()
102
114
)
103
- focusManager.moveFocus(FocusDirection .Next )
115
+ if (it.isNotEmpty() && character < WordMasterService .NUMBER_LETTERS - 1 ) {
116
+ // Only move within the same row; don't advance to the next row until the guess is submitted
117
+ focusManager.moveFocus(FocusDirection .Next )
118
+ }
104
119
}
105
120
},
106
- modifier = modifier,
121
+ modifier = modifier.onKeyEvent {
122
+ if (it.type == KeyEventType .KeyUp && (it.key == Key .Enter || it.key == Key .NumPadEnter )) {
123
+ if (guessAttempt == wordMasterService.currentGuessAttempt) {
124
+ var filled = true
125
+ for (c in 0 until WordMasterService .NUMBER_LETTERS ) {
126
+ if (boardGuesses[guessAttempt][c].isEmpty()) { filled = false ; break }
127
+ }
128
+ if (filled) {
129
+ wordMasterService.checkGuess()
130
+ // After submitting a guess, move focus to the next row's first cell
131
+ focusManager.moveFocus(FocusDirection .Next )
132
+ return @onKeyEvent true
133
+ }
134
+ }
135
+ }
136
+ false
137
+ }
138
+ .border(1 .dp, Color .Black .copy(alpha = 0.6f ), androidx.compose.foundation.shape.RoundedCornerShape (10 .dp)),
139
+ singleLine = true ,
140
+ keyboardOptions = KeyboardOptions (
141
+ capitalization = KeyboardCapitalization .Characters ,
142
+ imeAction = ImeAction .Done
143
+ ),
144
+ keyboardActions = KeyboardActions (
145
+ onDone = {
146
+ if (guessAttempt == wordMasterService.currentGuessAttempt) {
147
+ var filled = true
148
+ for (c in 0 until WordMasterService .NUMBER_LETTERS ) {
149
+ if (boardGuesses[guessAttempt][c].isEmpty()) { filled = false ; break }
150
+ }
151
+ if (filled) {
152
+ wordMasterService.checkGuess()
153
+ // After submitting a guess, move focus to the next row's first cell
154
+ focusManager.moveFocus(FocusDirection .Next )
155
+ }
156
+ }
157
+ }
158
+ ),
107
159
textStyle = TextStyle (fontSize = 14 .sp, textAlign = TextAlign .Center ),
160
+ shape = androidx.compose.foundation.shape.RoundedCornerShape (10 .dp),
108
161
colors = TextFieldDefaults .colors(
162
+ focusedTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
109
163
unfocusedTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
164
+ disabledTextColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
165
+ cursorColor = mapLetterStatusToTextColor(boardStatus[guessAttempt][character]),
166
+ focusedContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
110
167
unfocusedContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
168
+ disabledContainerColor = mapLetterStatusToBackgroundColor(boardStatus[guessAttempt][character]),
169
+ focusedIndicatorColor = Color .Transparent ,
170
+ unfocusedIndicatorColor = Color .Transparent ,
171
+ disabledIndicatorColor = Color .Transparent ,
172
+ errorIndicatorColor = Color .Transparent ,
111
173
),
112
174
)
113
175
@@ -121,9 +183,28 @@ fun WordMasterView(padding: Modifier) {
121
183
}
122
184
123
185
Spacer (Modifier .height(16 .dp))
186
+
187
+ if (revealedAnswer != null ) {
188
+ Text (
189
+ text = " Answer: $revealedAnswer " ,
190
+ style = TextStyle (fontSize = 18 .sp, color = MaterialTheme .colorScheme.onSurface)
191
+ )
192
+ Spacer (Modifier .height(12 .dp))
193
+ }
194
+
124
195
Row (horizontalArrangement = Arrangement .Center ) {
125
196
Button (onClick = {
126
- wordMasterService.checkGuess()
197
+ // Only submit and advance focus if the current row is filled
198
+ val current = wordMasterService.currentGuessAttempt
199
+ var filled = true
200
+ for (c in 0 until WordMasterService .NUMBER_LETTERS ) {
201
+ if (boardGuesses[current][c].isEmpty()) { filled = false ; break }
202
+ }
203
+ if (filled) {
204
+ wordMasterService.checkGuess()
205
+ // Move focus to next row's first cell
206
+ focusManager.moveFocus(FocusDirection .Next )
207
+ }
127
208
}) {
128
209
Text (" Guess" )
129
210
}
@@ -135,6 +216,23 @@ fun WordMasterView(padding: Modifier) {
135
216
Text (" New Game" )
136
217
}
137
218
}
219
+
220
+ if (lastGuessCorrect) {
221
+ androidx.compose.material3.AlertDialog (
222
+ onDismissRequest = { /* keep dialog until OK pressed */ },
223
+ title = { Text (" You win!" ) },
224
+ text = { Text (" Great job guessing the word." ) },
225
+ confirmButton = {
226
+ Button (onClick = {
227
+ wordMasterService.resetGame()
228
+ // Re-focus first cell after reset
229
+ focusRequester.requestFocus()
230
+ }) {
231
+ Text (" OK" )
232
+ }
233
+ }
234
+ )
235
+ }
138
236
}
139
237
}
140
238
0 commit comments