@@ -37,6 +37,7 @@ import androidx.compose.foundation.text.KeyboardActions
37
37
import androidx.compose.ui.unit.dp
38
38
import androidx.compose.ui.unit.sp
39
39
import androidx.compose.ui.input.key.onKeyEvent
40
+ import androidx.compose.ui.input.key.onPreviewKeyEvent
40
41
import androidx.compose.ui.input.key.Key
41
42
import androidx.compose.ui.input.key.KeyEventType
42
43
import androidx.compose.ui.input.key.key
@@ -85,7 +86,10 @@ fun WordMasterView(padding: Modifier) {
85
86
val lastGuessCorrect by wordMasterService.lastGuessCorrect.collectAsState()
86
87
87
88
val focusManager = LocalFocusManager .current
88
- val focusRequester = remember { FocusRequester () }
89
+ // FocusRequesters for every cell to enable precise intra-row navigation (e.g., Backspace behavior)
90
+ val cellRequesters = remember {
91
+ List (WordMasterService .MAX_NUMBER_OF_GUESSES ) { List (WordMasterService .NUMBER_LETTERS ) { FocusRequester () } }
92
+ }
89
93
90
94
Row (padding.fillMaxSize().padding(16 .dp), horizontalArrangement = Center , verticalAlignment = Alignment .CenterVertically ) {
91
95
@@ -98,43 +102,73 @@ fun WordMasterView(padding: Modifier) {
98
102
horizontalAlignment = Alignment .CenterHorizontally
99
103
) {
100
104
101
- var modifier = Modifier .width(55 .dp).height(55 .dp)
102
- if (guessAttempt == 0 && character == 0 ) {
103
- modifier = modifier.focusRequester(focusRequester)
104
- }
105
+ var modifier = Modifier .width(55 .dp).height(55 .dp).focusRequester(cellRequesters[guessAttempt][character])
105
106
106
107
TextField (
107
108
value = boardGuesses[guessAttempt][character],
108
- onValueChange = {
109
- if (it.length <= 1 && guessAttempt == wordMasterService.currentGuessAttempt) {
110
- wordMasterService.setGuess(
111
- guessAttempt,
112
- character,
113
- it.uppercase()
114
- )
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 )
109
+ onValueChange = { newValue ->
110
+ if (guessAttempt == wordMasterService.currentGuessAttempt) {
111
+ val upper = newValue.uppercase()
112
+ val capped = if (upper.length > 1 ) upper.substring(0 , 1 ) else upper
113
+ val previous = boardGuesses[guessAttempt][character]
114
+
115
+ if (capped != previous) {
116
+ wordMasterService.setGuess(
117
+ guessAttempt,
118
+ character,
119
+ capped
120
+ )
121
+ }
122
+
123
+ if (capped.isNotEmpty()) {
124
+ if (character < WordMasterService .NUMBER_LETTERS - 1 ) {
125
+ // Advance to next column in the same row
126
+ focusManager.moveFocus(FocusDirection .Next )
127
+ }
128
+ } else {
129
+ // If we deleted the last character in this cell, move back to previous cell in same row
130
+ if (previous.isNotEmpty() && character > 0 ) {
131
+ cellRequesters[guessAttempt][character - 1 ].requestFocus()
132
+ }
118
133
}
119
134
}
120
135
},
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 }
136
+ modifier = modifier
137
+ .onPreviewKeyEvent {
138
+ if (guessAttempt == wordMasterService.currentGuessAttempt && (it.key == Key .Backspace || it.key == Key .Delete ) && it.type == KeyEventType .KeyDown ) {
139
+ val currentVal = boardGuesses[guessAttempt][character]
140
+ if (currentVal.isEmpty() && character > 0 ) {
141
+ cellRequesters[guessAttempt][character - 1 ].requestFocus()
142
+ return @onPreviewKeyEvent true
127
143
}
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
144
+ }
145
+ false
146
+ }
147
+ .onKeyEvent {
148
+ if (it.type == KeyEventType .KeyUp && it.key == Key .Backspace ) {
149
+ if (guessAttempt == wordMasterService.currentGuessAttempt) {
150
+ val currentVal = boardGuesses[guessAttempt][character]
151
+ if (currentVal.isEmpty() && character > 0 ) {
152
+ cellRequesters[guessAttempt][character - 1 ].requestFocus()
153
+ return @onKeyEvent true
154
+ }
155
+ }
156
+ } else if (it.type == KeyEventType .KeyUp && (it.key == Key .Enter || it.key == Key .NumPadEnter )) {
157
+ if (guessAttempt == wordMasterService.currentGuessAttempt) {
158
+ var filled = true
159
+ for (c in 0 until WordMasterService .NUMBER_LETTERS ) {
160
+ if (boardGuesses[guessAttempt][c].isEmpty()) { filled = false ; break }
161
+ }
162
+ if (filled) {
163
+ wordMasterService.checkGuess()
164
+ // After submitting a guess, move focus to the next row's first cell
165
+ focusManager.moveFocus(FocusDirection .Next )
166
+ return @onKeyEvent true
167
+ }
133
168
}
134
169
}
170
+ false
135
171
}
136
- false
137
- }
138
172
.border(1 .dp, Color .Black .copy(alpha = 0.6f ), androidx.compose.foundation.shape.RoundedCornerShape (10 .dp)),
139
173
singleLine = true ,
140
174
keyboardOptions = KeyboardOptions (
@@ -173,9 +207,11 @@ fun WordMasterView(padding: Modifier) {
173
207
),
174
208
)
175
209
176
- DisposableEffect (Unit ) {
177
- focusRequester.requestFocus()
178
- onDispose { }
210
+ if (guessAttempt == 0 && character == 0 ) {
211
+ DisposableEffect (Unit ) {
212
+ cellRequesters[0 ][0 ].requestFocus()
213
+ onDispose { }
214
+ }
179
215
}
180
216
}
181
217
}
@@ -211,7 +247,7 @@ fun WordMasterView(padding: Modifier) {
211
247
Spacer (Modifier .width(16 .dp))
212
248
Button (onClick = {
213
249
wordMasterService.resetGame()
214
- focusRequester .requestFocus()
250
+ cellRequesters[ 0 ][ 0 ] .requestFocus()
215
251
}) {
216
252
Text (" New Game" )
217
253
}
@@ -226,7 +262,7 @@ fun WordMasterView(padding: Modifier) {
226
262
Button (onClick = {
227
263
wordMasterService.resetGame()
228
264
// Re-focus first cell after reset
229
- focusRequester .requestFocus()
265
+ cellRequesters[ 0 ][ 0 ] .requestFocus()
230
266
}) {
231
267
Text (" OK" )
232
268
}
0 commit comments