AGPL-3.0-only, a source link in the app, and everything on dev since #5 #6
@@ -20,7 +20,10 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
@@ -28,6 +31,7 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
@@ -241,6 +245,15 @@ private fun BlockField(
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
) {
|
||||
val style = textStyle.copy(color = MaterialTheme.colorScheme.onSurface)
|
||||
// Kept for the arrow keys, which ask which line the caret is on. Read only when
|
||||
// one is pressed, never while composing.
|
||||
var layout by remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||
val line = { offset: Int -> layout?.getLineForOffset(offset) ?: 0 }
|
||||
val arrows =
|
||||
Modifier.arrowKeysMoveCaret(
|
||||
atTop = { line(value.selection.end) == 0 },
|
||||
atBottom = { line(value.selection.end) == (layout?.lineCount ?: 1) - 1 },
|
||||
)
|
||||
Box(modifier = modifier) {
|
||||
if (hint != null && value.text.isEmpty()) {
|
||||
Text(
|
||||
@@ -252,12 +265,13 @@ private fun BlockField(
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().then(arrows),
|
||||
enabled = enabled,
|
||||
singleLine = singleLine,
|
||||
textStyle = style,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
onTextLayout = { layout = it },
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -54,7 +56,7 @@ fun PlainTextField(
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxWidth().arrowKeysMoveCaret(atTop = { singleLine }, atBottom = { singleLine }),
|
||||
enabled = enabled,
|
||||
placeholder = hint?.let { { Text(stringResource(it)) } },
|
||||
singleLine = singleLine,
|
||||
@@ -86,3 +88,29 @@ private fun plainFieldColors(): TextFieldColors =
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Arrow keys on a hardware keyboard move the caret, not the focus.
|
||||
*
|
||||
* Compose's text field gives the arrow keys of any device that reports D-pad keys
|
||||
* to focus search before the field sees them, and a physical keyboard with arrow
|
||||
* keys reports exactly that — so without this, ← and → jump to whatever sits beside
|
||||
* the field instead of moving through the text. Cancelling the search in a
|
||||
* direction hands the key back: the search reports that nothing moved, and the
|
||||
* field takes the key as editing.
|
||||
*
|
||||
* ← and → never leave the field. ↑ and ↓ move between its lines, and from the first
|
||||
* or last line — [atTop], [atBottom] — on to the field above or below, the way a
|
||||
* caret leaves one paragraph for the next. Both are asked at the moment of the key
|
||||
* press, so they can read where the caret is now.
|
||||
*/
|
||||
fun Modifier.arrowKeysMoveCaret(
|
||||
atTop: () -> Boolean,
|
||||
atBottom: () -> Boolean,
|
||||
): Modifier =
|
||||
focusProperties {
|
||||
left = FocusRequester.Cancel
|
||||
right = FocusRequester.Cancel
|
||||
if (!atTop()) up = FocusRequester.Cancel
|
||||
if (!atBottom()) down = FocusRequester.Cancel
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user