DecorationBox in Jetpack Compose BasicTextField - kotlin

I use jetpack compose create a editText,I want to show a hint like before "android:hint",
so I try to use decorationBox,but after I created it the input isn't displayed and the log can display my input content.
this is my code,
val passState= remember { mutableStateOf(TextFieldValue("")) }
BasicTextField(
decorationBox = {
Text("password",color = loginGrayColor)
},
value = passState.value,
onValueChange = { passState.value = it ; Log.d("password",it.text) },
singleLine = true,
maxLines = 1,
textStyle = TextStyle(
fontSize = 15.sp,
color = loginInputTextColor
),
modifier = Modifier
.padding(start = 10.dp, top = 10.dp)
.height(20.dp)
)

You have to add the innerTextField provided by the decorationBox.
Something like:
var value by remember { mutableStateOf(TextFieldValue("")) }
BasicTextField(
value = value,
onValueChange = { value = it },
decorationBox = { innerTextField ->
Row(
Modifier
.background(Color.LightGray, RoundedCornerShape(percent = 30))
.padding(16.dp)
) {
if (value.text.isEmpty()) {
Text("Label")
}
innerTextField() //<-- Add this
}
},
)
If you would like to have the cursor start at the beginning of the placeholder label, put the decorationBox content inside a Box rather than a Row.

Related

Push Front View up with Keyboard, don't push Background View up in Kotlin Compose

I want to do something like this:
Where there is a view that pops on top of the open keyboard.
I've tried to do this, and I have this so far:
However, when I put this view in a Box, as the second view, the first view is also pushed up, here's the code:
#OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
#ExperimentalComposeUiApi
fun Modifier.bringIntoViewAfterImeAnimation(): Modifier = composed {
var focusState by remember { mutableStateOf<FocusState?>(null) }
val relocationRequester = remember { BringIntoViewRequester() }
val isImeVisible = WindowInsets.isImeVisible
LaunchedEffect(
isImeVisible,
focusState,
relocationRequester
) {
if (isImeVisible && focusState?.isFocused == false) {
relocationRequester.bringIntoView()
}
relocationRequester.bringIntoView()
}
bringIntoViewRequester(relocationRequester).onFocusChanged { focusState = it }
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun SpaceCreator(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize().clip(RoundedCornerShape(10.dp)),
verticalArrangement = Arrangement.Bottom
) {
SpaceCreatorContainer()
}
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun SpaceCreatorContainer() {
Card(
modifier = Modifier
.bringIntoViewAfterImeAnimation()
.shadow(elevation = 10.dp, shape = RoundedCornerShape(10.dp), clip = true)
.background(color = colors.background)
) {
SpaceCreatorWrapper()
}
}
#Composable
fun SpaceCreatorWrapper() {
val localFocusManager = LocalFocusManager.current
Column(
modifier = Modifier.padding(15.dp).clip(RoundedCornerShape(10.dp))
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Top Text"
)
Text(text = "Content")
OutlinedTextField(
value = "ss",
onValueChange = { },
label = { Text("Email Address") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = { localFocusManager.clearFocus() }
)
)
OutlinedTextField(
value = "ss",
onValueChange = { },
label = { Text("Email Address") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { localFocusManager.clearFocus() }
)
)
Text(text = "Content")
}
}
That's the code for SpaceCreator(), and then I add it to a Box where it's supposed to float over another view, like so:
BoxWithConstraints(
propagateMinConstraints = true
) {
Box(
modifier = Modifier
.fillMaxSize()
) {
MainView()
SpaceCreator(navController = navController)
}
}
How do I only push up the second view in the Box when the keyboard is opened, and not the entire box?
Also,
Currently, I have a AnimationNavigation screen which is a Box(fillMaxSize), and the content of the keyboard modal stuck to the bottom. I also have a auto focus on the input, which is making the navigation a bit slow, unlike other apps I've seen.
Is there a smooth way to do this on low-mid-range devices like Samsung A23?
Thank you.

Cannot make ExposedDropdownMenu same width as OutlinedTextField

I faced the following problem - items of the dropdown are not the same width as OutlinedTextField
Looked for the solution - found the following:
Add the variable to keep textField width:
var textFieldSize by remember { mutableStateOf(Size.Zero) }
Set the value in the onGloballyPositioned of TextField
onGloballyPositioned { coordinates ->
textFieldSize = coordinates.size.toSize()
}
Read the value in ExposedDropdownMenu
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.width(with(LocalDensity.current) { textFieldSize.width.toDp() })
)
The problem is that it works fine with DropdownMenu, but doesn't work with ExposedDropdownMenu. What's the problem?
Here's the full code:
var expanded by remember { mutableStateOf(false) }
val sexList by remember { mutableStateOf(listOf("Male", "Female")) }
var textFieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (expanded)
Icons.Filled.ArrowDropUp
else
Icons.Filled.ArrowDropDown
ExposedDropdownMenuBox(
modifier = modifier
.clickable(onClick = { expanded = true }),
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
textFieldSize = coordinates.size.toSize()
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = BorderColor,
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = BrandColor,
focusedLabelColor = BrandColor,
),
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_complete_registration_sex),
contentDescription = null
)
},
trailingIcon = { Icon(icon, null) },
shape = RoundedCornerShape(Dimen.Dimen14),
label = {
Text(
"Choose Gender",
style = PoppinsNormalStyle14
)
},
readOnly = true
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.width(with(LocalDensity.current) { textFieldSize.width.toDp() })
) {
sexList.forEach {
DropdownMenuItem(
onClick = { expanded = false },
) {
Text(it, style = PoppinsNormalStyle12Gray2)
}
}
}
}
ExposedDropdownMenuBox is built to calculate width for you so you don't have to use all this onGloballyPositioned related logic.
The fact that it doesn't work is a known issue.
Until it's fixed it's recommended to use DropdownMenu with Modifier.exposedDropdownSize()(this modifier will apply the width calculated by ExposedDropdownMenuBox) instead of ExposedDropdownMenu:
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
sexList.forEach {
DropdownMenuItem(
onClick = { expanded = false },
) {
Text(it, style = PoppinsNormalStyle12Gray2)
}
}
}

Jetpack Compose - Disable TextField long press handler

I have an IconButton in the trailingIcon of OutlinedTextField like:
OutlinedTextField(
modifier = Modifier.weight(1f),
label = { Text(text = "Label") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(onClick = {
println("onClick")
}, onLongClick = {
println("onLongClick shows TextToolbar")
}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
}
)
IconButton2 is just a copy of IconButton but with combinedClickable to include onLongClick instead of clickable.
The problem is that when I long click IconButton2, it shows the TextToolbar for the TextField. Doesn't matter what I do, the text field will handle long click, show the TextToolbar and provide haptic feedback.
Even if I use pointerInput with awaitPointerEvent and consumeAllChanges (like here) it still triggers it. The TextField doesn't answer to any tap or anything but if I long click it, it answers!
The workaround I'm doing for now is wrapping the text field in a Row and add the IconButton beside it instead of "inside" but I needed to have the icon button as the trailingIcon.
Is there any way to properly do it?
Compose 1.0.3 and 1.1.0-alpha05 both behaves the same.
I ended up making a small hack that seems to be working fine, so basically I add a dummy Box as the trailingIcon to get the position of it, then I add an IconButton outside of it (both wrapped in a Box) and I also get the position of it + I offset it using the position of the dummy box. Not the ideal solution but works fine.
Here's the full source if anyone needs it:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colors.background
) {
var text by remember { mutableStateOf("") }
var trailingIconOffset by remember { mutableStateOf(Offset.Zero) }
var iconButtonOffset by remember { mutableStateOf(Offset.Zero) }
val colors = TextFieldDefaults.outlinedTextFieldColors()
Column {
//With hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "With hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
Box(modifier = IconButtonSizeModifier
.onGloballyPositioned {
trailingIconOffset = it.positionInRoot()
}
)
},
colors = colors
)
val contentColor by colors.trailingIconColor(
enabled = true,
isError = false
)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalContentAlpha provides contentColor.alpha
) {
IconButton2(
modifier = Modifier
.onGloballyPositioned {
iconButtonOffset = it.positionInRoot()
}
.absoluteOffset {
IntOffset(
(trailingIconOffset.x - iconButtonOffset.x).toInt(),
(trailingIconOffset.y - iconButtonOffset.y).toInt()
)
},
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
}
}
//Without hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Without hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
},
colors = colors
)
}
}
}
}
}
}
}
private val RippleRadius = 24.dp
private val IconButtonSizeModifier = Modifier.size(48.dp)
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun IconButton2(
modifier: Modifier = Modifier,
onClick: () -> Unit,
onLongClick: (() -> Unit)? = null,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: #Composable () -> Unit
) {
Box(
modifier = modifier
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = RippleRadius)
)
.then(IconButtonSizeModifier),
contentAlignment = Alignment.Center
) {
val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled
CompositionLocalProvider(LocalContentAlpha provides contentAlpha, content = content)
}
}

Box doesn't capture key events in Compose Desktop

Keys are printed when the TextField is focused but not when the Box itself if focused.
Box(
modifier = Modifier.onKeyEvent {
println(it.key)
false
}.fillMaxSize().focusable()
) {
val fieldValue = remember { mutableStateOf(TextFieldValue("")) }
TextField(
value = fieldValue.value,
onValueChange = { fieldValue.value = it }
)
}
Inspired by this answer, I change the code.
When you click on the Box, you remove the focus from the TextField but you don't give it to the Box. This has to be done manually.
val focusRequester = FocusRequester()
Box(
modifier = Modifier.onKeyEvent {
println(it.key)
false
}.fillMaxSize()
.focusRequester(focusRequester)
.focusable()
.clickable (
interactionSource = remember { MutableInteractionSource() },
indication = null // To disable the ripple effect
) {
focusRequester.requestFocus()
}
) {
val fieldValue = remember { mutableStateOf(TextFieldValue("")) }
TextField(
value = fieldValue.value,
onValueChange = { fieldValue.value = it }
)
}

Aligning a resized TextField in Jetpack Compose

I am having problem aligning a resized TextField in Jetpack Compose for Desktop. When I resize the width of the TextField, the TextField automatically adjust itself to center position on screen. I have tried using Modify.Align and it did not work.
Can someone help? Here is my code
#Composable
fun addSales(sales: Sales,
actionChangeSales: (sales: Sales) -> Unit,
actionQuantityInc: (() -> Unit),
actionItemNameInc: (() -> Unit)){
Column{
TextField(
value = sales.itemName,
textStyle = TextStyle(color = Color.DarkGray),
onValueChange = {
actionChangeSales(Sales(itemName = it))
actionItemNameInc.invoke()
},
label = { Text("Item name")}
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = roundQuantity(sales.quantitySold),
textStyle = TextStyle(color = Color.DarkGray),
onValueChange = {
actionChangeSales(Sales(quantitySold = getDoubleFromEditText(it)))
actionQuantityInc.invoke()
},
label = { Text("Quantity")},
modifier = Modifier.width(120.dp)
)
}
}
As a workaround, try to wrap OutlinedTextField with Box and apply the width modifier there:
Box(modifier = Modifier.width(120.dp)) {
OutlinedTextField(
value = "123456789",
textStyle = TextStyle(color = Color.DarkGray),
onValueChange = {},
label = { Text("Quantity") },
)
}
You have to wrap them in a Column again.
Pseudo code as below
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifiler.fillMaxWidth() // needed so it knows the full width to center the content within it
) {
// This wraps your content and centers it
Column( horizontalAlignment = Alignment.Start ){
// This wraps and aligns the content to the left/start
// place your content here
}
}