Skip to content

Forms : introduce RadioButtonInput #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ import com.arcgismaps.mapping.featureforms.ComboBoxFormInput
import com.arcgismaps.mapping.featureforms.DateTimePickerFormInput
import com.arcgismaps.mapping.featureforms.FeatureForm
import com.arcgismaps.mapping.featureforms.FieldFormElement
import com.arcgismaps.mapping.featureforms.RadioButtonsFormInput
import com.arcgismaps.mapping.featureforms.TextAreaFormInput
import com.arcgismaps.mapping.featureforms.TextBoxFormInput
import com.arcgismaps.toolkit.featureforms.components.FieldElement
import com.arcgismaps.toolkit.featureforms.components.base.BaseFieldState
import com.arcgismaps.toolkit.featureforms.components.codedvalue.rememberCodedValueFieldState
import com.arcgismaps.toolkit.featureforms.components.datetime.rememberDateTimeFieldState
import com.arcgismaps.toolkit.featureforms.components.codedvalue.rememberRadioButtonFieldState
import com.arcgismaps.toolkit.featureforms.components.text.rememberFormTextFieldState
import kotlinx.coroutines.CoroutineScope
import java.util.Objects
Expand Down Expand Up @@ -193,6 +195,14 @@ private fun rememberFieldStates(
)
}

is RadioButtonsFormInput -> {
rememberRadioButtonFieldState(
field = fieldElement,
form = form,
scope = scope
)
}

else -> {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import com.arcgismaps.mapping.featureforms.ComboBoxFormInput
import com.arcgismaps.mapping.featureforms.DateTimePickerFormInput
import com.arcgismaps.mapping.featureforms.FeatureForm
import com.arcgismaps.mapping.featureforms.FieldFormElement
import com.arcgismaps.mapping.featureforms.FormInputNoValueOption
import com.arcgismaps.mapping.featureforms.GroupFormElement
import com.arcgismaps.mapping.featureforms.RadioButtonsFormInput
import com.arcgismaps.mapping.featureforms.TextAreaFormInput
import com.arcgismaps.mapping.featureforms.TextBoxFormInput
import com.arcgismaps.toolkit.featureforms.components.base.BaseFieldState
import com.arcgismaps.toolkit.featureforms.components.codedvalue.ComboBoxField
import com.arcgismaps.toolkit.featureforms.components.codedvalue.CodedValueFieldState
import com.arcgismaps.toolkit.featureforms.components.datetime.DateTimeField
import com.arcgismaps.toolkit.featureforms.components.datetime.DateTimeFieldState
import com.arcgismaps.toolkit.featureforms.components.codedvalue.RadioButtonField
import com.arcgismaps.toolkit.featureforms.components.codedvalue.RadioButtonFieldState
import com.arcgismaps.toolkit.featureforms.components.text.FormTextField
import com.arcgismaps.toolkit.featureforms.components.text.FormTextFieldState

Expand All @@ -35,6 +39,14 @@ internal fun FieldElement(field: FieldFormElement, state: BaseFieldState) {
ComboBoxField(state = state as CodedValueFieldState)
}

is RadioButtonsFormInput -> {
if ((state as RadioButtonFieldState).shouldFallback()) {
ComboBoxField(state = state)
} else {
RadioButtonField(state = state)
}
}

else -> { /* TO-DO: add support for other input types */
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright 2023 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.arcgismaps.toolkit.featureforms.components.codedvalue

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.arcgismaps.mapping.featureforms.FormInputNoValueOption
import com.arcgismaps.toolkit.featureforms.R

@Composable
internal fun RadioButtonField(
state: RadioButtonFieldState,
modifier: Modifier = Modifier,
colors: RadioButtonFieldColors = RadioButtonFieldDefaults.colors()
) {
val value by state.value.collectAsState()
val editable by state.isEditable.collectAsState()
val required by state.isRequired.collectAsState()
val noValueLabel = state.noValueLabel.ifEmpty { stringResource(R.string.no_value) }
RadioButtonField(
label = state.label,
description = state.description,
value = value,
editable = editable,
required = required,
codedValues = state.codedValues.associateBy({ it.code }, { it.name }),
showNoValueOption = state.showNoValueOption,
noValueLabel = noValueLabel,
modifier = modifier,
colors = colors
) {
state.onValueChanged(it)
}
}

@Composable
private fun RadioButtonField(
label: String,
description: String,
value: String,
editable: Boolean,
required: Boolean,
codedValues: Map<Any?, String>,
showNoValueOption: FormInputNoValueOption,
noValueLabel: String,
modifier: Modifier = Modifier,
colors: RadioButtonFieldColors = RadioButtonFieldDefaults.colors(),
onValueChanged: (String) -> Unit = {}
) {
val options = if (!required) {
if (showNoValueOption == FormInputNoValueOption.Show) {
mapOf("" to noValueLabel) + codedValues
} else codedValues
} else codedValues

Column(
modifier = modifier
.fillMaxWidth()
.padding(start = 15.dp, end = 15.dp, top = 10.dp, bottom = 10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.Start
) {
Text(
text = if (required) {
"$label *"
} else {
label
},
style = MaterialTheme.typography.bodyMedium,
color = colors.labelColor(enabled = editable)
)
Column(
modifier = Modifier
.selectableGroup()
.border(
width = 1.dp,
color = colors.containerBorderColor(enabled = editable),
shape = RoundedCornerShape(5.dp)
)
) {
CompositionLocalProvider(
LocalContentColor provides colors.textColor(enabled = editable)
) {
options.forEach { (code, name) ->
RadioButtonRow(
value = name,
selected = (code?.toString()
?: "") == value || (name == noValueLabel && value.isEmpty()),
enabled = editable,
onClick = { onValueChanged(code?.toString() ?: "") }
)
}
}
}
if (description.isNotEmpty()) {
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = colors.supportingTextColor(enabled = editable)
)
}
}

}

@Composable
private fun RadioButtonRow(
value: String,
selected: Boolean,
enabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.selectable(
selected = selected,
enabled = enabled,
role = Role.RadioButton,
onClick = onClick,
)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
RadioButton(
selected = selected,
onClick = null,
enabled = enabled
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium
)
}
}

@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF, showSystemUi = true)
@Composable
private fun RadioButtonFieldPreview() {
MaterialTheme {
RadioButtonField(
label = "A list of values",
description = "Description",
value = "",
editable = true,
required = true,
codedValues = mapOf(
"One" to "One",
"Two" to "Two",
"Three" to "Three"
),
showNoValueOption = FormInputNoValueOption.Show,
noValueLabel = "No Value",
) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2023 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.arcgismaps.toolkit.featureforms.components.codedvalue

import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color

internal object RadioButtonFieldDefaults {

private const val textDisabledAlpha = 0.38f
private const val containerDisabledAlpha = 0.12f

@Composable
fun colors(): RadioButtonFieldColors = RadioButtonFieldColors(
defaultLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurface.copy(
alpha = textDisabledAlpha
),
defaultSupportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledSupportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = textDisabledAlpha
),
errorColor = MaterialTheme.colorScheme.error,
defaultContainerBorderColor = MaterialTheme.colorScheme.outline,
disabledContainerBorderColor = MaterialTheme.colorScheme.outline.copy(
alpha = containerDisabledAlpha
),
defaultTextColor = LocalContentColor.current,
disabledTextColor = LocalContentColor.current.copy(alpha = textDisabledAlpha)
)
}

internal data class RadioButtonFieldColors(
val defaultLabelColor: Color,
val disabledLabelColor: Color,
val defaultSupportingTextColor: Color,
val disabledSupportingTextColor: Color,
val errorColor: Color,
val defaultContainerBorderColor: Color,
val disabledContainerBorderColor: Color,
val defaultTextColor: Color,
val disabledTextColor: Color
) {
/**
* Represents the color used for the label of this radio button field.
*
* @param enabled whether the field is enabled
*/
@Composable
fun labelColor(enabled: Boolean): Color {
return if (enabled) {
defaultLabelColor
} else {
disabledLabelColor
}
}

/**
* Represents the color used for the supporting text of this radio button field.
*
* @param enabled whether the field is enabled
*/
@Composable
fun supportingTextColor(enabled: Boolean): Color {
return if (enabled) {
defaultSupportingTextColor
} else {
disabledSupportingTextColor
}
}

/**
* Represents the color used for the container border of this radio button field.
*
* @param enabled whether the field is enabled
*/
@Composable
fun containerBorderColor(enabled: Boolean): Color {
return if (enabled) {
defaultContainerBorderColor
} else {
disabledContainerBorderColor
}
}

/**
* Represents the color used for the text of this radio button field options.
*
* @param enabled whether the field is enabled
*/
@Composable
fun textColor(enabled: Boolean): Color {
return if (enabled) {
defaultTextColor
} else {
disabledTextColor
}
}
}
Loading