From 7d1ea983ba0e610234a208a132482c96e503385b Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Tue, 31 Oct 2023 14:21:45 -0700 Subject: [PATCH 1/6] expect a formatted value to be UTC --- .../featureforms/components/datetime/DateTimeFieldState.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt index d7f0b4bda..58d96d38f 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt @@ -40,9 +40,9 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.time.LocalDateTime +import java.time.ZoneOffset import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException -import java.util.TimeZone internal class DateTimeFieldProperties( label: String, @@ -176,12 +176,12 @@ internal fun rememberDateTimeFieldState( * * @since 200.3.0 */ -internal fun dateTimeFromString(formattedDateTime: String): Long? { +internal fun dateTimeFromString(formattedDateTime: String, offset: ZoneOffset = ZoneOffset.UTC): Long? { return if (formattedDateTime.isNotEmpty()) { try { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") LocalDateTime.parse(formattedDateTime, formatter) - .atZone(TimeZone.getDefault().toZoneId()) + .atZone(offset) .toInstant() .toEpochMilli() } catch (ex: DateTimeParseException) { From 5a0af26a1a3bd9455fbf8981ccaaa3d8b36540d9 Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Wed, 1 Nov 2023 12:44:35 -0700 Subject: [PATCH 2/6] make BaseFieldState generic --- .../featureforms/components/base/BaseFieldState.kt | 14 +++++++------- .../components/text/FormTextFieldState.kt | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt index 4d7039456..403d66f7a 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt @@ -27,11 +27,11 @@ import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn -internal open class FieldProperties( +internal open class FieldProperties( val label: String, val placeholder: String, val description: String, - val value: StateFlow, + val value: StateFlow, val required: StateFlow, val editable: StateFlow, val visible: StateFlow @@ -48,9 +48,9 @@ internal open class FieldProperties( * @param onEditValue a callback to invoke when the user edits result in a change of value. This * is called on [BaseFieldState.onValueChanged]. */ -internal open class BaseFieldState( - properties: FieldProperties, - initialValue: String = properties.value.value, +internal open class BaseFieldState( + properties: FieldProperties, + initialValue: T = properties.value.value, scope: CoroutineScope, protected val onEditValue: (Any?) -> Unit, ) { @@ -76,7 +76,7 @@ internal open class BaseFieldState( * Current value state for the field. */ @OptIn(ExperimentalCoroutinesApi::class) - val value: StateFlow = flowOf(_value, properties.value.drop(1)) + val value: StateFlow = flowOf(_value, properties.value.drop(1)) .flattenMerge() .stateIn(scope, SharingStarted.Eagerly, initialValue) @@ -98,7 +98,7 @@ internal open class BaseFieldState( /** * Callback to update the current value of the FormTextFieldState to the given [input]. */ - open fun onValueChanged(input: String) { + open fun onValueChanged(input: T) { onEditValue(input) _value.value = input } diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt index cb5f801df..c1719a8fe 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt @@ -33,9 +33,9 @@ import com.arcgismaps.mapping.featureforms.FieldFormElement import com.arcgismaps.mapping.featureforms.TextAreaFormInput import com.arcgismaps.mapping.featureforms.TextBoxFormInput import com.arcgismaps.toolkit.featureforms.R -import com.arcgismaps.toolkit.featureforms.components.formelement.FieldElement import com.arcgismaps.toolkit.featureforms.components.base.BaseFieldState import com.arcgismaps.toolkit.featureforms.components.base.FieldProperties +import com.arcgismaps.toolkit.featureforms.components.formelement.FieldElement import com.arcgismaps.toolkit.featureforms.components.text.ValidationErrorState.ExactCharConstraint import com.arcgismaps.toolkit.featureforms.components.text.ValidationErrorState.MaxCharConstraint import com.arcgismaps.toolkit.featureforms.components.text.ValidationErrorState.MaxNumericConstraint @@ -75,7 +75,7 @@ internal class TextFieldProperties( val singleLine: Boolean, val minLength: Int, val maxLength: Int, -) : FieldProperties(label, placeholder, description, value, required, editable, visible) +) : FieldProperties(label, placeholder, description, value, required, editable, visible) /** * A class to handle the state of a [FormTextField]. Essential properties are inherited from the @@ -96,7 +96,7 @@ internal class FormTextFieldState( scope: CoroutineScope, private val context: Context, onEditValue: (Any?) -> Unit -) : BaseFieldState( +) : BaseFieldState( properties = properties, initialValue = initialValue, scope = scope, From 939cd80596d3d11a2e6d2ef5f4a7de79ba38d0b4 Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Wed, 1 Nov 2023 16:04:39 -0700 Subject: [PATCH 3/6] make DateTimeFieldState vary with Instant? make DateTimeField work with Instant, not Long. --- .../toolkit/featureforms/FeatureForm.kt | 12 +-- .../components/base/BaseFieldState.kt | 4 +- .../components/base/BaseGroupState.kt | 6 +- .../codedvalue/CodedValueFieldState.kt | 4 +- .../components/datetime/DateTimeField.kt | 13 +-- .../components/datetime/DateTimeFieldState.kt | 86 ++++++------------- .../components/datetime/DateUtil.kt | 7 +- .../datetime/picker/DateTimePicker.kt | 9 +- .../datetime/picker/DateTimePickerState.kt | 46 +++++----- .../components/formelement/FieldElement.kt | 4 +- .../components/formelement/GroupElement.kt | 6 +- .../toolkit/featureforms/utils/Dialog.kt | 9 +- 12 files changed, 88 insertions(+), 118 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureForm.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureForm.kt index be4487722..b3070d56b 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureForm.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureForm.kt @@ -199,10 +199,10 @@ internal fun FeatureFormContent( @Composable private fun FeatureFormBody( form: FeatureForm, - fieldStateMap: Map, + fieldStateMap: Map>, groupStateMap: Map, modifier: Modifier = Modifier, - onFieldDialogRequest: ((BaseFieldState, Int) -> Unit)? = null + onFieldDialogRequest: ((BaseFieldState<*>, Int) -> Unit)? = null ) { val lazyListState = rememberLazyListState() Column( @@ -290,8 +290,8 @@ internal fun rememberFieldStates( elements: List, context: Context, scope: CoroutineScope -): Map { - val stateMap = mutableMapOf() +): Map> { + val stateMap = mutableMapOf>() elements.forEach { element -> if (element is FieldFormElement) { val state = when (element.input) { @@ -320,8 +320,8 @@ internal fun rememberFieldStates( val input = element.input as DateTimePickerFormInput rememberDateTimeFieldState( field = element, - minEpochMillis = input.min?.toEpochMilli(), - maxEpochMillis = input.max?.toEpochMilli(), + minEpochMillis = input.min, + maxEpochMillis = input.max, shouldShowTime = input.includeTime, form = form, scope = scope diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt index 403d66f7a..3e8fb0d23 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseFieldState.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn -internal open class FieldProperties( +internal open class FieldProperties( val label: String, val placeholder: String, val description: String, @@ -48,7 +48,7 @@ internal open class FieldProperties( * @param onEditValue a callback to invoke when the user edits result in a change of value. This * is called on [BaseFieldState.onValueChanged]. */ -internal open class BaseFieldState( +internal open class BaseFieldState( properties: FieldProperties, initialValue: T = properties.value.value, scope: CoroutineScope, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseGroupState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseGroupState.kt index 0b292943f..ac6fa7855 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseGroupState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/base/BaseGroupState.kt @@ -33,7 +33,7 @@ internal class GroupProperties( internal class BaseGroupState( properties: GroupProperties, - val fieldStates: Map + val fieldStates: Map?> ) { val label = properties.label @@ -47,7 +47,7 @@ internal class BaseGroupState( } companion object { - fun Saver(fieldStates: Map): Saver = listSaver( + fun Saver(fieldStates: Map?>): Saver = listSaver( save = { listOf(it.label, it.description, it.expanded.value) }, @@ -69,7 +69,7 @@ internal class BaseGroupState( @Composable internal fun rememberBaseGroupState( groupElement: GroupFormElement, - fieldStates: Map + fieldStates: Map?> ): BaseGroupState = rememberSaveable( saver = BaseGroupState.Saver(fieldStates) ) { diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt index fda492eba..dc6649109 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt @@ -49,7 +49,7 @@ internal open class CodedValueFieldProperties( val codedValues: List, val showNoValueOption: FormInputNoValueOption, val noValueLabel: String -) : FieldProperties(label, placeholder, description, value, required, editable, visible) +) : FieldProperties(label, placeholder, description, value, required, editable, visible) /** * A class to handle the state of a [ComboBoxField]. Essential properties are inherited @@ -68,7 +68,7 @@ internal open class CodedValueFieldState( initialValue: String = properties.value.value, scope: CoroutineScope, onEditValue: ((Any?) -> Unit) -) : BaseFieldState( +) : BaseFieldState( properties = properties, scope = scope, initialValue = initialValue, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt index aefac5ec4..5bf5a1924 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.arcgismaps.toolkit.featureforms.R import com.arcgismaps.toolkit.featureforms.components.base.BaseTextField import kotlinx.coroutines.flow.MutableStateFlow +import java.time.Instant @Composable internal fun DateTimeField( @@ -50,7 +51,7 @@ internal fun DateTimeField( ) { val isEditable by state.isEditable.collectAsState() val isRequired by state.isRequired.collectAsState() - val epochMillis by state.epochMillis.collectAsState() + val instant by state.value.collectAsState() val interactionSource = remember { MutableInteractionSource() } // to check if the field was ever focused by the user var wasFocused by rememberSaveable { mutableStateOf(false) } @@ -61,9 +62,11 @@ internal fun DateTimeField( } BaseTextField( - text = epochMillis?.formattedDateTime(state.shouldShowTime) ?: "", + text = instant?.formattedDateTime(state.shouldShowTime) ?: "", onValueChange = { - state.onValueChanged(it) + it.toLongOrNull()?.also { millis -> + state.onValueChanged(Instant.ofEpochMilli(millis)) + } ?: state.onValueChanged(null) }, modifier = modifier, readOnly = true, @@ -75,7 +78,7 @@ internal fun DateTimeField( trailingIcon = Icons.Rounded.EditCalendar, supportingText = { // if the field was focused and is required, validate the current value - if (wasFocused && isRequired && epochMillis == null) { + if (wasFocused && isRequired && instant == null) { Text( text = stringResource(id = R.string.required), color = MaterialTheme.colorScheme.error @@ -111,7 +114,7 @@ private fun DateTimeFieldPreview() { label = "Launch Date and Time", placeholder = "", description = "Enter the date for apollo 11 launch", - value = MutableStateFlow(""), + value = MutableStateFlow(null), editable = MutableStateFlow(true), required = MutableStateFlow(false), visible = MutableStateFlow(true), diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt index 58d96d38f..90fdbbd3e 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt @@ -18,7 +18,6 @@ package com.arcgismaps.toolkit.featureforms.components.datetime -import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver @@ -31,31 +30,26 @@ import com.arcgismaps.toolkit.featureforms.components.base.FieldProperties import com.arcgismaps.toolkit.featureforms.components.text.FormTextFieldState import com.arcgismaps.toolkit.featureforms.components.text.TextFieldProperties import com.arcgismaps.toolkit.featureforms.utils.editValue -import com.arcgismaps.toolkit.featureforms.utils.formattedValueFlow import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import java.time.LocalDateTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException +import java.time.Instant internal class DateTimeFieldProperties( label: String, placeholder: String, description: String, - value: StateFlow, + value: StateFlow, required: StateFlow, editable: StateFlow, visible: StateFlow, - val minEpochMillis: Long?, - val maxEpochMillis: Long?, + val minEpochMillis: Instant?, + val maxEpochMillis: Instant?, val shouldShowTime: Boolean -) : FieldProperties(label, placeholder, description, value, required, editable, visible) +) : FieldProperties(label, placeholder, description, value, required, editable, visible) /** * A class to handle the state of a [DateTimeField]. Essential properties are inherited from the @@ -70,35 +64,31 @@ internal class DateTimeFieldProperties( */ internal class DateTimeFieldState( properties: DateTimeFieldProperties, - initialValue: String = properties.value.value, + initialValue: Instant? = properties.value.value, scope: CoroutineScope, onEditValue: (Any?) -> Unit -) : BaseFieldState( +) : BaseFieldState( properties = properties, initialValue = initialValue, scope = scope, onEditValue = onEditValue ) { - val minEpochMillis: Long? = properties.minEpochMillis + val minEpochMillis: Instant? = properties.minEpochMillis - val maxEpochMillis: Long? = properties.maxEpochMillis + val maxEpochMillis: Instant? = properties.maxEpochMillis val shouldShowTime: Boolean = properties.shouldShowTime - - @OptIn(ExperimentalCoroutinesApi::class) - val epochMillis: StateFlow = value.mapLatest { - if (it.toLongOrNull() != null) { - it.toLong() - } else { - dateTimeFromString(it) - } - }.stateIn( - scope, - started = SharingStarted.Eagerly, - initialValue = dateTimeFromString(value.value) - ) - + companion object { + internal fun dateTimeValue(value: StateFlow, scope: CoroutineScope): StateFlow = + value.map { + if (it is Instant?) { + it + } else { + throw IllegalArgumentException("expected a date time for the value of a date time field") + } + }.stateIn(scope, SharingStarted.Eagerly, value.value as Instant?) + fun Saver( field: FieldFormElement, form: FeatureForm, @@ -114,12 +104,12 @@ internal class DateTimeFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = dateTimeValue(field.value, scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, - minEpochMillis = input.min?.toEpochMilli(), - maxEpochMillis = input.max?.toEpochMilli(), + minEpochMillis = input.min, + maxEpochMillis = input.max, shouldShowTime = input.includeTime ), initialValue = list[0], @@ -137,8 +127,8 @@ internal class DateTimeFieldState( @Composable internal fun rememberDateTimeFieldState( field: FieldFormElement, - minEpochMillis: Long?, - maxEpochMillis: Long?, + minEpochMillis: Instant?, + maxEpochMillis: Instant?, shouldShowTime: Boolean, form: FeatureForm, scope: CoroutineScope @@ -154,7 +144,7 @@ internal fun rememberDateTimeFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = DateTimeFieldState.dateTimeValue(field.value, scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, @@ -170,27 +160,3 @@ internal fun rememberDateTimeFieldState( ) } -/** - * Maps the [FieldFormElement.value] from a String to Long? - * Empty strings are made to be null Longs. - * - * @since 200.3.0 - */ -internal fun dateTimeFromString(formattedDateTime: String, offset: ZoneOffset = ZoneOffset.UTC): Long? { - return if (formattedDateTime.isNotEmpty()) { - try { - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") - LocalDateTime.parse(formattedDateTime, formatter) - .atZone(offset) - .toInstant() - .toEpochMilli() - } catch (ex: DateTimeParseException) { - Log.e( - "DateTimeFieldState", - "dateTimeFromString: Error parsing $formattedDateTime into a valid date time", - ex - ) - null - } - } else null -} diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateUtil.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateUtil.kt index 81f025a1b..94772b856 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateUtil.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateUtil.kt @@ -66,20 +66,19 @@ internal fun Long.toDateMillis(): Long { } /** - * Formats epoch milliseconds for the current timezone + * Formats an Instant for the current timezone * * @param includeTime format the time if true * @return a string formatted for the value in epoch milliseconds * @since 200.3.0 */ -internal fun Long.formattedDateTime(includeTime: Boolean): String { - +internal fun Instant.formattedDateTime(includeTime: Boolean): String { val formatter = if (includeTime) { DateTimeFormatter.ofPattern("MMM dd, yyyy h:mm a") } else { DateTimeFormatter.ofPattern("MMM dd, yyyy") } - return this.toZonedDateTime().format(formatter) + return atZone(TimeZone.getDefault().toZoneId()).format(formatter) } /** diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePicker.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePicker.kt index 7381fef09..8dce2415f 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePicker.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePicker.kt @@ -60,9 +60,8 @@ import androidx.compose.ui.window.DialogProperties import com.arcgismaps.toolkit.featureforms.R import com.arcgismaps.toolkit.featureforms.components.datetime.picker.time.TimePicker import com.arcgismaps.toolkit.featureforms.components.datetime.picker.time.TimePickerState -import com.arcgismaps.toolkit.featureforms.components.datetime.toDateMillis -import com.arcgismaps.toolkit.featureforms.components.datetime.toZonedDateTime import java.time.Instant +import java.util.TimeZone /** * Defines the style of [DateTimePicker]. @@ -120,9 +119,9 @@ internal fun DateTimePicker( } // calculate the date ranges from the state val datePickerRange = IntRange( - start = state.minDateTime?.toZonedDateTime()?.year + start = state.minDateTime?.atZone(TimeZone.getDefault().toZoneId())?.year ?: DatePickerDefaults.YearRange.first, - endInclusive = state.maxDateTime?.toZonedDateTime()?.year + endInclusive = state.maxDateTime?.atZone(TimeZone.getDefault().toZoneId())?.year ?: DatePickerDefaults.YearRange.last ) // The picker input type, date or time. @@ -134,7 +133,7 @@ internal fun DateTimePicker( DatePickerState( initialSelectedDateMillis = dateTime.dateForPicker, initialDisplayedMonthMillis = dateTime.dateForPicker - ?: (state.minDateTime ?: state.maxDateTime), + ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), datePickerRange, DisplayMode.Picker ) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt index d9d021ef8..7d90055d4 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt @@ -122,12 +122,12 @@ internal interface DateTimePickerState { /** * Minimum date time allowed in milliseconds. This should be null if no range restriction is needed. */ - val minDateTime: Long? + val minDateTime: Instant? /** * Maximum date time allowed in milliseconds. This should be null if no range restriction is needed. */ - val maxDateTime: Long? + val maxDateTime: Instant? /** * The current date time value. Use [setDateTime] to set this state. @@ -216,22 +216,22 @@ internal interface DateTimePickerState { */ private class DateTimePickerStateImpl( override val pickerStyle: DateTimePickerStyle, - override val minDateTime: Long?, - override val maxDateTime: Long?, - initialValue: Long?, + override val minDateTime: Instant?, + override val maxDateTime: Instant?, + initialValue: Instant?, override val label: String, override val description: String = "", pickerInput: DateTimePickerInput ) : DateTimePickerState { override var dateTime = mutableStateOf( - UtcDateTime.create(initialValue) + UtcDateTime.create(initialValue?.toEpochMilli()) ) override val selectedDateTimeMillis: Long? get() = dateTime.value.epochMillis override val timeZone: TimeZone = TimeZone.getDefault() - override val timeZoneOffset = initialValue?.let { timeZone.getOffset(it) } ?: 0 + override val timeZoneOffset = initialValue?.toEpochMilli()?.let { timeZone.getOffset(it) } ?: 0 override val activePickerInput = mutableStateOf(pickerInput) @@ -254,11 +254,11 @@ private class DateTimePickerStateImpl( // to UTC. val utcDateTime = timeStamp.minus(timeStamp.defaultTimeZoneOffset) - return minDateTime?.let { min -> - maxDateTime?.let { max -> + return minDateTime?.toEpochMilli()?.let { min -> + maxDateTime?.toEpochMilli()?.let { max -> utcDateTime in min..max } ?: (utcDateTime >= min) - } ?: maxDateTime?.let { + } ?: maxDateTime?.toEpochMilli()?.let { utcDateTime <= it } ?: true } @@ -269,8 +269,8 @@ private class DateTimePickerStateImpl( // To compare it to min and max, the input must be converted // to UTC. val utcDate = UtcDateTime.create(timeStamp.minus(timeStamp.defaultTimeZoneOffset)).date!! - val minDate = UtcDateTime.create(minDateTime).date - val maxDate = UtcDateTime.create(maxDateTime).date + val minDate = UtcDateTime.create(minDateTime?.toEpochMilli()).date + val maxDate = UtcDateTime.create(maxDateTime?.toEpochMilli()).date return minDate?.let { min -> maxDate?.let { max -> @@ -314,9 +314,9 @@ private class DateTimePickerStateImpl( */ internal fun DateTimePickerState( style: DateTimePickerStyle, - minDateTime: Long? = null, - maxDateTime: Long? = null, - initialValue: Long? = null, + minDateTime: Instant? = null, + maxDateTime: Instant? = null, + initialValue: Instant? = null, label: String, description: String = "", pickerInput: DateTimePickerInput @@ -346,9 +346,9 @@ internal fun DateTimePickerState( @Composable internal fun rememberDateTimePickerState( style: DateTimePickerStyle, - minDateTime: Long? = null, - maxDateTime: Long? = null, - initialValue: Long? = null, + minDateTime: Instant? = null, + maxDateTime: Instant? = null, + initialValue: Instant? = null, label: String, description: String = "", pickerInput: DateTimePickerInput @@ -373,8 +373,8 @@ internal fun rememberDateTimePickerState( internal fun dateTimePickerStateSaver(): Saver = listSaver( save = { listOf(it.pickerStyle, - it.minDateTime, - it.maxDateTime, + it.minDateTime?.toEpochMilli(), + it.maxDateTime?.toEpochMilli(), it.dateTime.value.epochMillis, it.label, it.description, @@ -387,9 +387,9 @@ internal fun dateTimePickerStateSaver(): Saver = listS // not on initial conditions. DateTimePickerStateImpl( it[0] as DateTimePickerStyle, - it[1] as Long?, - it[2] as Long?, - it[3] as Long?, + it[1]?.let { Instant.ofEpochMilli(it as Long) }, + it[2]?.let { Instant.ofEpochMilli(it as Long) }, + it[3]?.let { Instant.ofEpochMilli(it as Long) }, it[4] as String, it[5] as String, it[6] as DateTimePickerInput diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/FieldElement.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/FieldElement.kt index 46ed8ebe2..e55d3d780 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/FieldElement.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/FieldElement.kt @@ -32,8 +32,8 @@ import com.arcgismaps.toolkit.featureforms.components.text.FormTextField import com.arcgismaps.toolkit.featureforms.components.text.FormTextFieldState @Composable -internal fun FieldElement( - state: BaseFieldState, +internal fun FieldElement( + state: BaseFieldState, onDialogRequest: () -> Unit = {} ) { val visible by state.isVisible.collectAsState() diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/GroupElement.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/GroupElement.kt index d7b4c2e6b..0c48351a3 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/GroupElement.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/formelement/GroupElement.kt @@ -50,7 +50,7 @@ internal fun GroupElement( state: BaseGroupState, modifier: Modifier = Modifier, colors: GroupElementColors = GroupElementDefaults.colors(), - onDialogRequest: (BaseFieldState, Int) -> Unit + onDialogRequest: (BaseFieldState<*>, Int) -> Unit ) { val visible by groupElement.isVisible.collectAsState() if (visible) { @@ -74,11 +74,11 @@ private fun GroupElement( label: String, description: String, expanded: Boolean, - fieldStates: Map, + fieldStates: Map?>, modifier: Modifier = Modifier, colors: GroupElementColors, onClick: () -> Unit, - onDialogRequest: ((BaseFieldState, Int) -> Unit)? = null + onDialogRequest: ((BaseFieldState<*>, Int) -> Unit)? = null ) { Card( modifier = modifier, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/Dialog.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/Dialog.kt index 71a8e8f9c..a16f33d0b 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/Dialog.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/Dialog.kt @@ -31,6 +31,7 @@ import com.arcgismaps.toolkit.featureforms.components.datetime.picker.DateTimePi import com.arcgismaps.toolkit.featureforms.components.datetime.picker.DateTimePickerStyle import com.arcgismaps.toolkit.featureforms.components.datetime.picker.rememberDateTimePickerState import java.io.Serializable +import java.time.Instant /** * Specifies the type of dialog to use for a [FeatureFormDialog]. @@ -87,7 +88,7 @@ internal sealed class DialogType : Serializable { @Composable internal fun FeatureFormDialog( dialogType: DialogType, - state: BaseFieldState?, + state: BaseFieldState<*>?, onDismissRequest: () -> Unit ) { when (dialogType) { @@ -109,7 +110,7 @@ internal fun FeatureFormDialog( pickerStyle, state.minEpochMillis, state.maxEpochMillis, - state.epochMillis.collectAsState().value, + state.value.collectAsState().value, state.label, state.description, DateTimePickerInput.Date @@ -120,7 +121,9 @@ internal fun FeatureFormDialog( onDismissRequest = onDismissRequest, onCancelled = onDismissRequest, onConfirmed = { - state.onValueChanged(pickerState.selectedDateTimeMillis.toString()) + state.onValueChanged(pickerState.selectedDateTimeMillis?.let { + Instant.ofEpochMilli(it) + }) onDismissRequest() } ) From 18ed06621e864a4e943ab5b9992a8b531d97a571 Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Thu, 2 Nov 2023 10:22:46 -0700 Subject: [PATCH 4/6] only support date time text field value changes that clear the text. --- .../featureforms/components/datetime/DateTimeField.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt index 5bf5a1924..7a9d756fa 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.tooling.preview.Preview import com.arcgismaps.toolkit.featureforms.R import com.arcgismaps.toolkit.featureforms.components.base.BaseTextField import kotlinx.coroutines.flow.MutableStateFlow -import java.time.Instant @Composable internal fun DateTimeField( @@ -64,9 +63,9 @@ internal fun DateTimeField( BaseTextField( text = instant?.formattedDateTime(state.shouldShowTime) ?: "", onValueChange = { - it.toLongOrNull()?.also { millis -> - state.onValueChanged(Instant.ofEpochMilli(millis)) - } ?: state.onValueChanged(null) + // the only allowable change is to clear the text + require(it.isEmpty()) + state.onValueChanged(null) }, modifier = modifier, readOnly = true, From 837c169ff2414f9d92289473b9c3e3d1ac740728 Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Fri, 3 Nov 2023 13:31:15 -0700 Subject: [PATCH 5/6] PR review suggestions --- .../featureforms/components/datetime/DateTimeField.kt | 5 +++-- .../components/datetime/picker/DateTimePickerState.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt index 7a9d756fa..77d775e5b 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeField.kt @@ -64,8 +64,9 @@ internal fun DateTimeField( text = instant?.formattedDateTime(state.shouldShowTime) ?: "", onValueChange = { // the only allowable change is to clear the text - require(it.isEmpty()) - state.onValueChanged(null) + if (it.isEmpty()) { + state.onValueChanged(null) + } }, modifier = modifier, readOnly = true, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt index 7d90055d4..9ef967e24 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/picker/DateTimePickerState.kt @@ -120,12 +120,12 @@ internal class UtcDateTime private constructor( internal interface DateTimePickerState { /** - * Minimum date time allowed in milliseconds. This should be null if no range restriction is needed. + * Minimum date time allowed. This should be null if no range restriction is needed. */ val minDateTime: Instant? /** - * Maximum date time allowed in milliseconds. This should be null if no range restriction is needed. + * Maximum date time allowed. This should be null if no range restriction is needed. */ val maxDateTime: Instant? From a640de022a7849cedee5d68db5337391ca21e577 Mon Sep 17 00:00:00 2001 From: Soren Roth Date: Fri, 3 Nov 2023 14:29:11 -0700 Subject: [PATCH 6/6] fuse usage of formattedValue and value into one type safe generic function --- .../codedvalue/CodedValueFieldState.kt | 6 +++--- .../codedvalue/RadioButtonFieldState.kt | 6 +++--- .../components/codedvalue/SwitchFieldState.kt | 6 +++--- .../components/datetime/DateTimeFieldState.kt | 17 +++-------------- .../components/text/FormTextFieldState.kt | 6 +++--- .../featureforms/utils/CorePrototypes.kt | 12 ++++++++++-- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt index dc6649109..9e129af13 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/CodedValueFieldState.kt @@ -32,7 +32,7 @@ import com.arcgismaps.toolkit.featureforms.components.base.FieldProperties import com.arcgismaps.toolkit.featureforms.components.text.TextFieldProperties import com.arcgismaps.toolkit.featureforms.utils.editValue import com.arcgismaps.toolkit.featureforms.utils.fieldType -import com.arcgismaps.toolkit.featureforms.utils.formattedValueFlow +import com.arcgismaps.toolkit.featureforms.utils.valueFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -134,7 +134,7 @@ internal open class CodedValueFieldState( label = formElement.label, placeholder = formElement.hint, description = formElement.description, - value = formElement.formattedValueFlow(scope), + value = formElement.valueFlow(scope), editable = formElement.isEditable, required = formElement.isRequired, visible = formElement.isVisible, @@ -169,7 +169,7 @@ internal fun rememberCodedValueFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/RadioButtonFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/RadioButtonFieldState.kt index ae7ed1ca4..7b1c2fbde 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/RadioButtonFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/RadioButtonFieldState.kt @@ -25,7 +25,7 @@ import com.arcgismaps.mapping.featureforms.FieldFormElement import com.arcgismaps.mapping.featureforms.RadioButtonsFormInput import com.arcgismaps.toolkit.featureforms.utils.editValue import com.arcgismaps.toolkit.featureforms.utils.fieldType -import com.arcgismaps.toolkit.featureforms.utils.formattedValueFlow +import com.arcgismaps.toolkit.featureforms.utils.valueFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -79,7 +79,7 @@ internal class RadioButtonFieldState( label = formElement.label, placeholder = formElement.hint, description = formElement.description, - value = formElement.formattedValueFlow(scope), + value = formElement.valueFlow(scope), editable = formElement.isEditable, required = formElement.isRequired, visible = formElement.isVisible, @@ -114,7 +114,7 @@ internal fun rememberRadioButtonFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/SwitchFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/SwitchFieldState.kt index 848d1b9be..9e91508f6 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/SwitchFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/codedvalue/SwitchFieldState.kt @@ -31,7 +31,7 @@ import com.arcgismaps.toolkit.featureforms.components.base.BaseFieldState import com.arcgismaps.toolkit.featureforms.utils.editValue import com.arcgismaps.toolkit.featureforms.utils.fieldIsNullable import com.arcgismaps.toolkit.featureforms.utils.fieldType -import com.arcgismaps.toolkit.featureforms.utils.formattedValueFlow +import com.arcgismaps.toolkit.featureforms.utils.valueFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -121,7 +121,7 @@ internal class SwitchFieldState( label = formElement.label, placeholder = formElement.hint, description = formElement.description, - value = formElement.formattedValueFlow(scope), + value = formElement.valueFlow(scope), editable = formElement.isEditable, required = formElement.isRequired, visible = formElement.isVisible, @@ -166,7 +166,7 @@ internal fun rememberSwitchFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt index 90fdbbd3e..0300fa942 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/datetime/DateTimeFieldState.kt @@ -30,11 +30,9 @@ import com.arcgismaps.toolkit.featureforms.components.base.FieldProperties import com.arcgismaps.toolkit.featureforms.components.text.FormTextFieldState import com.arcgismaps.toolkit.featureforms.components.text.TextFieldProperties import com.arcgismaps.toolkit.featureforms.utils.editValue +import com.arcgismaps.toolkit.featureforms.utils.valueFlow import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.time.Instant @@ -80,15 +78,6 @@ internal class DateTimeFieldState( val shouldShowTime: Boolean = properties.shouldShowTime companion object { - internal fun dateTimeValue(value: StateFlow, scope: CoroutineScope): StateFlow = - value.map { - if (it is Instant?) { - it - } else { - throw IllegalArgumentException("expected a date time for the value of a date time field") - } - }.stateIn(scope, SharingStarted.Eagerly, value.value as Instant?) - fun Saver( field: FieldFormElement, form: FeatureForm, @@ -104,7 +93,7 @@ internal class DateTimeFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = dateTimeValue(field.value, scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, @@ -144,7 +133,7 @@ internal fun rememberDateTimeFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = DateTimeFieldState.dateTimeValue(field.value, scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt index c1719a8fe..e8edbcd05 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/components/text/FormTextFieldState.kt @@ -51,10 +51,10 @@ import com.arcgismaps.toolkit.featureforms.utils.asLongTuple import com.arcgismaps.toolkit.featureforms.utils.domain import com.arcgismaps.toolkit.featureforms.utils.editValue import com.arcgismaps.toolkit.featureforms.utils.fieldType -import com.arcgismaps.toolkit.featureforms.utils.formattedValueFlow import com.arcgismaps.toolkit.featureforms.utils.isFloatingPoint import com.arcgismaps.toolkit.featureforms.utils.isIntegerType import com.arcgismaps.toolkit.featureforms.utils.isNumeric +import com.arcgismaps.toolkit.featureforms.utils.valueFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -404,7 +404,7 @@ internal class FormTextFieldState( label = formElement.label, placeholder = formElement.hint, description = formElement.description, - value = formElement.formattedValueFlow(scope), + value = formElement.valueFlow(scope), required = formElement.isRequired, editable = formElement.isEditable, visible = formElement.isVisible, @@ -447,7 +447,7 @@ internal fun rememberFormTextFieldState( label = field.label, placeholder = field.hint, description = field.description, - value = field.formattedValueFlow(scope), + value = field.valueFlow(scope), editable = field.isEditable, required = field.isRequired, visible = field.isVisible, diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/CorePrototypes.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/CorePrototypes.kt index 0b57cf75a..9f7f3d1c1 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/CorePrototypes.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/utils/CorePrototypes.kt @@ -72,8 +72,16 @@ internal fun FeatureForm.fieldType(element: FieldFormElement): FieldType { internal fun FeatureForm.domain(element: FieldFormElement): Domain? = feature.featureTable?.getField(element.fieldName)?.domain -internal fun FieldFormElement.formattedValueFlow(scope: CoroutineScope): StateFlow = - value.map { formattedValue }.stateIn(scope, SharingStarted.Eagerly, formattedValue) +internal inline fun FieldFormElement.valueFlow(scope: CoroutineScope): StateFlow = + if (value.value is T) { + value.map { it as T }.stateIn(scope, SharingStarted.Eagerly, value.value as T) + } else if (formattedValue is T) { + // T is String + value.map { formattedValue as T }.stateIn(scope, SharingStarted.Eagerly, formattedValue as T) + } else { + // usage error. + throw IllegalStateException("the generic parameterization of the state object must match either the value or the formattedValue.") + } internal val FieldType.isNumeric: Boolean