Jetpack Compose: Textfield and FAB not using full width - android

I am trying to put a TextField and a FAB inside a bottomBar using Jetpack Compose.
I wrapped the two with a box, which has the modifier "fillMaxWidth".
But the two controls dont use the full width.
Does anyone know, how to fix this issue?
Here is my Code:
#Composable
fun ChatView() {
Scaffold(
topBar= { ChannelButton() },
bottomBar = { ChatBox() },
modifier = Modifier
.padding(10.dp)
) {
ChatList()
}
}
#Composable
fun ChatBox() {
Box(modifier = Modifier
.background(DiscordDarkGray)
.fillMaxWidth()
){
Column(modifier = Modifier
.padding(10.dp)
.fillMaxWidth()) {
HorizontalCenteredRow(modifier = Modifier
.fillMaxWidth()) {
val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
value = textState.value,
onValueChange = { textState.value = it }
)
Spacer(modifier = Modifier.width(10.dp))
FloatingIconActionButton (
icon = Icons.Default.Send,
onClick = { /*TODO*/ },
backgroundColor = DiscordBlue
)
}
Spacer(modifier = Modifier.height(60.dp))
}
}
}
Here is the Code of the HorizontalCenteredRow:
#Composable
fun HorizontalCenteredRow(
modifier: Modifier = Modifier,
content: #Composable RowScope.() -> Unit
) {
Row(
modifier = modifier
.wrapContentSize(Alignment.Center),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content
)
}
Here is the code of the FAB:
#Composable
fun FloatingIconActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
backgroundColor: Color = MaterialTheme.colors.secondary,
contentColor: Color = contentColorFor(backgroundColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
icon: ImageVector = Icons.Default.QuestionAnswer,
iconContentDescription: String = "",
) {
Surface(
modifier = modifier.let {
if (enabled) {
it.clickable(
onClick = onClick,
role = Role.Button,
interactionSource = interactionSource,
indication = null
)
} else it
},
shape = shape,
color = backgroundColor,
contentColor = contentColor,
elevation = elevation.elevation(interactionSource).value
) {
CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) {
ProvideTextStyle(MaterialTheme.typography.button) {
Box(
modifier = Modifier
.defaultMinSize(minWidth = FabSize, minHeight = FabSize)
.indication(interactionSource, rememberRipple()),
contentAlignment = Alignment.Center
) {
Icon(
icon,
iconContentDescription,
tint = if (enabled) {
colors().onPrimary
} else {
colors().onPrimary.transparentize(.6f)
}
)
}
}
}
}
}

Using
HorizontalCenteredRow(
modifier = Modifier.fillMaxWidth()
the Row fills all the space, but it doesn't mean that the children occupy the entire space.
If you want to fill all the space with the children you have to change the default dimensions of them.
For example you can apply modifier = Modifier.weight(1f) to the TextField.
Something like:
HorizontalCenteredRow(modifier = Modifier
.fillMaxWidth()) {
val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
value = textState.value,
onValueChange = { textState.value = it },
modifier = Modifier.weight(1f)
)

Related

Unable to align a text and a Switch in a Row lambda in jetpack compose

ISSUE
I am trying to align the text Enable Notifications and the switch on the same line. If I were to use XML layouts, then I would have gone with constraint layout, but I am trying to figure out how to do this with Compose.
SET UP
#Composable
fun Settings() {
val viewModel: SettingsViewModel = viewModel()
val uiState: SettingsState = viewModel.uiState.collectAsState().value
SettingsList(uiState = uiState, viewModel)
}
#Composable
fun SettingsList(uiState: SettingsState, viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
val scaffoldState = rememberScaffoldState()
Scaffold(
modifier = Modifier, scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.colors.background,
topBar = {
TopAppBar(
modifier = Modifier.semantics {
heading()
},
backgroundColor = MaterialTheme.colors.surface,
contentPadding = PaddingValues(start = 12.dp)
) {
Icon(
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.header_settings_back)
)
Spacer(modifier = modifier.width(16.dp))
Text(
text = stringResource(id = R.string.header_settings),
fontSize = 18.sp,
color = MaterialTheme.colors.onSurface
)
}
}
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState).padding(paddingValues = it)
) {
NotificationSettingsComposable(uiState.notificationsEnabled, {
viewModel.toggleNotificationSettings()
}, Modifier.fillMaxWidth())
}
}
}
#Composable
fun NotificationSettingsComposable(checked: Boolean, onCheckedChanged: (Boolean) -> Unit, modifier: Modifier = Modifier) {
Surface(modifier = modifier.padding(12.dp)) {
Row {
Text(
text = stringResource(id = R.string.body_enable_notifications), fontSize = 16.sp,
color = MaterialTheme.colors.onSurface,
)
Switch(checked = checked, onCheckedChange = onCheckedChanged, modifier = Modifier.align(Alignment.Top))
}
}
}
However the text and the switch are not being aligned on the same line.
DESIRED OUTCOME
The text and the switch should be aligned on the same line as shown in this picture.
This is what I am getting
What I want to achieve
Add verticalAlignment = Alignment.CenterVertically in the Row:
Something like:
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable Notification", fontSize = 16.sp,
)
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}

Open ModalSheetLayout on TextField focus instead of Keyboard

I am working with Jetpack Compose and I have a page with several TextFields, where I want that, in several of them, when I click on the input, instead of appearing the keyboard, a ModalSheetLayout appears with different layouts that I have. Is this possible? I'm still not able to do this, all I can do is simple things when the focus changes. Can anyone help me?
The below sample should give a basic idea of how to do this.
Code
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun BottomSheetSelectionDemo() {
val coroutineScope: CoroutineScope = rememberCoroutineScope()
val modalBottomSheetState: ModalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
val colors = arrayListOf("Red", "Green", "Blue", "White", "Black")
val (value, setValue) = remember {
mutableStateOf(colors[0])
}
val toggleModalBottomSheetState = {
coroutineScope.launch {
if (!modalBottomSheetState.isAnimationRunning) {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.hide()
} else {
modalBottomSheetState.show()
}
}
}
}
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
LazyColumn {
items(colors) {
Text(
text = it,
modifier = Modifier
.fillMaxWidth()
.clickable {
setValue(it)
toggleModalBottomSheetState()
}
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
)
}
}
},
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize(),
) {
MyReadOnlyTextField(
value = value,
label = "Select a color",
onClick = {
toggleModalBottomSheetState()
},
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 4.dp,
),
)
}
}
}
#Composable
fun MyReadOnlyTextField(
modifier: Modifier = Modifier,
value: String,
label: String,
onClick: () -> Unit,
) {
Box(
modifier = modifier,
) {
androidx.compose.material3.OutlinedTextField(
value = value,
onValueChange = {},
modifier = Modifier
.fillMaxWidth(),
label = {
Text(
text = label,
)
},
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(
onClick = onClick,
),
)
}
}

Divider is not showing in the custom Jetpack Compose TextField

I have a custom TextField which has no problem in the single TextField but when I create a Row() and put two custom TextField inside that, the divider is gone.
My custom text field: I created a BasicTextField and set a column BorderColumnState() to draw border when the text field is focus
#Composable
fun InputTextField(
modifier: Modifier = Modifier,
textFieldValue: TextFieldValue = TextFieldValue(""),
labelText: String,
withBorderModifier: Modifier = Modifier,
enabled: Boolean = true,
dividerColor: Color,
dividerThickness: Dp = 0.5.dp,
spacer: Dp,
textStyle: TextStyle,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
isDropDown: Boolean = false,
valueChange: (String) -> Unit
) {
var value by remember { mutableStateOf(textFieldValue) }
//This is used if we want to use it in DropDown
if (textFieldValue.text.isNotEmpty() && isDropDown)
value = textFieldValue
///////////////////////////////////////////
val dividerState = remember {
mutableStateOf(true)
}
BasicTextField(
value = value,
onValueChange = {
value = it
valueChange.invoke(it.text)
},
modifier = Modifier
.onFocusChanged {
dividerState.value = !it.isFocused
},
decorationBox = { innerTextField ->
val mainModifier = if (!dividerState.value) {
withBorderModifier
} else {
modifier
}
BorderColumnState(
mainModifier,
labelText,
textStyle,
spacer,
innerTextField,
dividerState,
dividerThickness,
dividerColor
)
}, keyboardOptions = keyboardOptions, enabled = enabled
)
}
#Composable
private fun BorderColumnState(
modifier: Modifier,
labelText: String,
textStyle: TextStyle,
spacer: Dp,
innerTextField: #Composable () -> Unit,
dividerState: MutableState<Boolean>,
dividerThickness: Dp,
dividerColor: Color
) {
Column(
modifier = modifier.wrapContentHeight(),
horizontalAlignment = Alignment.Start
) {
Text(text = labelText, style = textStyle)
Spacer(modifier = Modifier.size(spacer))
innerTextField()
if (dividerState.value) {
Spacer(
modifier = modifier
.size(dividerThickness)
.background(dividerColor)
)
}
}
}
Usage custom text field: When I want to use two custom text field inside a Row, the divider is gone
#Composable
fun EditProfileScreen() {
val disableButtonState = remember {
mutableStateOf(false)
}
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 24.dp, end = 24.dp, top = 32.5.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CountryCodeAndMobileNumber(
countryCodeList = listOf("+98"),
clientInfo,
disableButtonState,
viewModel
)
Spacer(modifier = Modifier.size(16.dp))
InputTextField(
modifier = Modifier.fillMaxWidth(),
textFieldValue = TextFieldValue("sasasasas#gmail.com"),
labelText = stringResource(R.string.email_address),
withBorderModifier = Modifier
.fillMaxWidth()
.border(
width = 1.dp,
shape = AppShapes.small,
color = AppColor.brandColor.BLUE_DE_FRANCE
)
.padding(4.dp),
textStyle = AppFont.PoppinsTypography.caption,
dividerColor = AppColor.neutralColor.SILVER_CHALICE,
spacer = 8.dp,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
) {}
}
}
}
#Composable
private fun CountryCodeAndMobileNumber(
countryCodeList: List<String>,
clientInfo: Client,
) {
var mobileClientInfo = clientInfo
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
CountryCodeDropDown(
list = countryCodeList,
label = stringResource(
id = R.string.country
),
dividerColor = AppColor.neutralColor.SILVER_CHALICE,
enabled = false
)
Spacer(modifier = Modifier.size(4.dp))
InputTextField(
textFieldValue = TextFieldValue("111111"),
labelText = stringResource(R.string.mobile_number),
withBorderModifier = Modifier
.wrapContentWidth()
.border(
width = 1.dp,
shape = AppShapes.small,
color = AppColor.brandColor.BLUE_DE_FRANCE
)
.padding(4.dp),
enabled = false,
textStyle = AppFont.PoppinsTypography.caption,
dividerColor = AppColor.neutralColor.SILVER_CHALICE,
spacer = 8.dp,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
) {
}
}
}
How can I solve this problem?
val mainModifier = if (!dividerState.value) {
withBorderModifier
} else {
modifier
}
You can not assign modifier as a variable. that's why it's not working.
Edit your code like this:
BorderColumnState(
Modifier.then(if (!dividerState.value) withBorderModifier else modifier),
labelText,
textStyle,
spacer,
innerTextField,
dividerState,
dividerThickness,
dividerColor
)
I checked the code on my IDE. Why are you setting the size for the divider?
I changed the spacer to this and that's working fine.
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(dividerThickness)
.background(dividerColor)
)

How to remove padding between AlertDialog and title/text with Compose

Using compose, I want to create something like this :
Problem is using compose AlertDialog I only achieve to get this :
There is a padding between the AlertDialog border and the title that can not be removed. How to remove it using Compose ?
Using Modifier.padding(all = 0.dp) doesn't work. I tried it at every level.
Properties "title" and "text" are wrapped with AlertDialogBaselineLayout that is adding padding. You can't remove it.
I have made a custom Composable that is acting as an AlertDialog and doesn't use AlertDialogBaselineLayout.
You can copy/paste it and edit it according to your needs :
NoPaddingAlertDialog :
#Composable
fun NoPaddingAlertDialog(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
title: #Composable (() -> Unit)? = null,
text: #Composable (() -> Unit)? = null,
confirmButton: #Composable () -> Unit,
dismissButton: #Composable (() -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.medium,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
properties: DialogProperties = DialogProperties()
) {
Dialog(
onDismissRequest = onDismissRequest,
properties = properties
) {
Surface(
modifier = modifier,
shape = shape,
color = backgroundColor,
contentColor = contentColor
) {
Column(
modifier = Modifier
.fillMaxWidth()
) {
title?.let {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
val textStyle = MaterialTheme.typography.subtitle1
ProvideTextStyle(textStyle, it)
}
}
text?.let {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
val textStyle = MaterialTheme.typography.subtitle1
ProvideTextStyle(textStyle, it)
}
}
Box(
Modifier
.fillMaxWidth()
.padding(all = 8.dp)
) {
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
dismissButton?.invoke()
confirmButton()
}
}
}
}
}
}
You can then easily use it this way :
#Composable
fun MyCustomAlertDialog(openDialog: MutableState<Boolean> = mutableStateOf(true)) {
if (openDialog.value) {
NoPaddingAlertDialog(
title = {
Surface(
color = Color.Blue,
contentColor = Color.White,
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = " Popup Title️",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 16.dp),
)
}
},
text = {
Column(Modifier.fillMaxWidth()) {
OutlinedTextField(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = 8.dp, vertical = 16.dp)
.fillMaxWidth(),
value = "Alert Dialog content ...",
onValueChange = { },
label = { Text(text = "Content") },
)
}
},
onDismissRequest = { /*TODO*/ },
confirmButton = {
PopupButton(title = "Ok", setShow = {
openDialog.value = false
})
},
dismissButton = {
PopupButton(
title = "Cancel",
setShow = {
openDialog.value = false
}
)
}
)
}
}
And get this :
You can use a Dialog() instead of AlertDialog(). It allows you to pass in a composable as the content parameter, so you can set whatever padding you want. Just don't forget to set the background color in the root composable of the content, since it is transparent by default.

Create chip with outline Jetpack Compose

I have the following composable function to build a Chip:
#Composable
fun CategoryChip(
category: String,
isSelected: Boolean = false,
onSelectedCategoryChanged: (String) -> Unit,
onExecuteSearch: () -> Unit
) {
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
) {
Row(modifier = Modifier
.toggleable(
value = isSelected,
onValueChange = {
onSelectedCategoryChanged(category)
onExecuteSearch()
}
)) {
Text(
text = category,
style = MaterialTheme.typography.body2,
color = Color.White,
modifier = Modifier.padding(8.dp)
)
}
}
}
This creates the following chip:
But what I am trying to achieve is the following:
Is it possible to create a shape like that with Jetpack Compose?
Starting with M2 1.2.0-alpha02 you can use the Chip or FilterChip composable:
Chip(
onClick = { /* Do something! */ },
border = BorderStroke(
ChipDefaults.OutlinedBorderSize,
Color.Red
),
colors = ChipDefaults.chipColors(
backgroundColor = Color.White,
contentColor = Color.Red
),
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description"
)
}
) {
Text("Change settings")
}
With M3 (androidx.compose.material3) you can use one of these options:
AssistChip
FilterChip
InputChip
SuggestionChip
Something like:
AssistChip(
onClick = { /* Do something! */ },
label = { Text("Assist Chip") },
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description",
Modifier.size(AssistChipDefaults.IconSize)
)
}
)
Yes, all you have to do is add a border to your surface.
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
border = BorderStroke(
width = 1.dp,
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
)
)
Building on Code Poet's answer i wanted to show how to do a Material Chip with background color:
#Composable
fun buildChip(label: String, icon: ImageVector? = null) {
Box(modifier = Modifier.padding(8.dp)) {
Surface(
elevation = 1.dp,
shape = MaterialTheme.shapes.small,
color = Color.LightGray
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (icon != null) Icon(
icon,
modifier = Modifier
.fillMaxHeight()
.padding(horizontal = 4.dp)
)
Text(
label,
modifier = Modifier.padding(8.dp),
style = MaterialTheme.typography.button.copy(color = Color.DarkGray)
)
}
}
}
}
Simply use ChipDefaults.outlinedBorder and Defaults.outlinedChipColors():
Chip(
onClick = {},
border = ChipDefaults.outlinedBorder,
colors = ChipDefaults.outlinedChipColors(),
) {
Text(
text = "Trends",
)
}
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1
Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
We will use SuggestionChip composable function to create chips.
#Composable
fun SuggestionChipLayout() {
val chips by remember { mutableStateOf(listOf("India", "France", "Spain","Netherland","Austarlia","Nepal")) }
var chipState by remember { mutableStateOf("") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Suggestion Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chips.forEach {
SuggestionChipEachRow(chip = it, it == chipState) { chip ->
chipState = chip
}
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SuggestionChipEachRow(
chip: String,
selected: Boolean,
onChipState: (String) -> Unit
) {
SuggestionChip(onClick = {
if (!selected)
onChipState(chip)
else
onChipState("")
}, label = {
Text(text = chip)
},
border = SuggestionChipDefaults.suggestionChipBorder(
borderWidth = 1.dp,
borderColor = if (selected) Color.Transparent else PurpleGrey40
),
modifier = Modifier.padding(horizontal = 16.dp),
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = if (selected) Purple80 else Color.Transparent
),
shape = RoundedCornerShape(16.dp)
)
}
Filter Chips: In filter chip we can select multiple items at a time
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1 Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
we will use FilterChip composable function to create Filter Chip
#SuppressLint("MutableCollectionMutableState")
#Composable
fun FilterChipLayout() {
val originalChips by remember {
mutableStateOf(
listOf(
"chip1",
"chip2",
"chip3",
"chip4",
"chip5"
)
)
}
val temp: Set<Int> = emptySet()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Filter Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FilterChipEachRow(chipList = originalChips, tempList = temp)
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun FilterChipEachRow(
chipList: List<String>,
tempList: Set<Int>
) {
var multipleChecked by rememberSaveable { mutableStateOf(tempList) }
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chipList.forEachIndexed { index, s ->
FilterChip(selected = multipleChecked.contains(index), onClick = {
multipleChecked = if (multipleChecked.contains(index))
multipleChecked.minus(index)
else
multipleChecked.plus(index)
}, label = {
Text(text = s)
},
border = FilterChipDefaults.filterChipBorder(
borderColor = if (!multipleChecked.contains(index)) PurpleGrey40 else Color.Transparent,
borderWidth = if (multipleChecked.contains(index)) 0.dp else 2.dp
),
shape = RoundedCornerShape(8.dp),
leadingIcon = {
(if (multipleChecked.contains(index)) Icons.Default.Check else null)?.let {
Icon(
it,
contentDescription = ""
)
}
}
)
}
}
}

Categories

Resources