@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.height
14
14
import androidx.compose.foundation.layout.padding
15
15
import androidx.compose.foundation.layout.width
16
16
import androidx.compose.foundation.lazy.LazyColumn
17
- import androidx.compose.foundation.lazy.items
18
17
import androidx.compose.foundation.lazy.rememberLazyListState
19
18
import androidx.compose.material3.CircularProgressIndicator
20
19
import androidx.compose.material3.Divider
@@ -39,15 +38,15 @@ import com.arcgismaps.mapping.featureforms.ComboBoxFormInput
39
38
import com.arcgismaps.mapping.featureforms.DateTimePickerFormInput
40
39
import com.arcgismaps.mapping.featureforms.FeatureForm
41
40
import com.arcgismaps.mapping.featureforms.FieldFormElement
42
- import com.arcgismaps.mapping.featureforms.FormElement
43
41
import com.arcgismaps.mapping.featureforms.GroupFormElement
44
42
import com.arcgismaps.mapping.featureforms.RadioButtonsFormInput
45
43
import com.arcgismaps.mapping.featureforms.SwitchFormInput
46
44
import com.arcgismaps.mapping.featureforms.TextAreaFormInput
47
45
import com.arcgismaps.mapping.featureforms.TextBoxFormInput
48
46
import com.arcgismaps.toolkit.featureforms.components.base.BaseFieldState
49
- import com.arcgismaps.toolkit.featureforms.components.base.BaseGroupState
50
- import com.arcgismaps.toolkit.featureforms.components.base.FormElementState
47
+ import com.arcgismaps.toolkit.featureforms.components.base.MutableStateCollection
48
+ import com.arcgismaps.toolkit.featureforms.components.base.FormStateCollection
49
+ import com.arcgismaps.toolkit.featureforms.components.base.getState
51
50
import com.arcgismaps.toolkit.featureforms.components.base.rememberBaseGroupState
52
51
import com.arcgismaps.toolkit.featureforms.components.codedvalue.rememberCodedValueFieldState
53
52
import com.arcgismaps.toolkit.featureforms.components.codedvalue.rememberRadioButtonFieldState
@@ -59,7 +58,6 @@ import com.arcgismaps.toolkit.featureforms.components.text.rememberFormTextField
59
58
import com.arcgismaps.toolkit.featureforms.utils.FeatureFormDialog
60
59
import kotlinx.coroutines.CoroutineScope
61
60
import kotlinx.coroutines.delay
62
- import java.util.Objects
63
61
64
62
/* *
65
63
* A composable Form toolkit component that enables users to edit field values of features in a
@@ -135,20 +133,10 @@ internal fun FeatureFormContent(
135
133
) {
136
134
val context = LocalContext .current
137
135
val scope = rememberCoroutineScope()
138
- val fieldStateMap = rememberFieldStates(
139
- form = form,
140
- elements = form.elements,
141
- context = context,
142
- scope = scope
143
- )
144
- val groupStateMap = rememberGroupStates(
145
- form = form,
146
- context = context,
147
- scope = scope
148
- )
136
+ val states = rememberStates(form = form, context = context, scope = scope)
149
137
FeatureFormBody (
150
138
form = form,
151
- states = fieldStateMap + groupStateMap ,
139
+ states = states ,
152
140
modifier = modifier
153
141
)
154
142
FeatureFormDialog ()
@@ -157,7 +145,7 @@ internal fun FeatureFormContent(
157
145
@Composable
158
146
private fun FeatureFormBody (
159
147
form : FeatureForm ,
160
- states : Map < Int , FormElementState > ,
148
+ states : FormStateCollection ,
161
149
modifier : Modifier = Modifier
162
150
) {
163
151
val lazyListState = rememberLazyListState()
@@ -178,28 +166,19 @@ private fun FeatureFormBody(
178
166
modifier = Modifier .fillMaxSize(),
179
167
state = lazyListState
180
168
) {
181
- items(form.elements) { formElement ->
182
- when (formElement) {
183
- is FieldFormElement -> {
184
- val state = states[formElement.id] as ? BaseFieldState <* >
185
- if (state != null ) {
186
- FieldElement (state = state)
169
+ states.forEach { entry ->
170
+ item {
171
+ when (entry.formElement) {
172
+ is FieldFormElement -> {
173
+ FieldElement (state = entry.getState<BaseFieldState <* >>())
187
174
}
188
- }
189
175
190
- is GroupFormElement -> {
191
- val state = states[formElement.id] as ? BaseGroupState
192
- if (state != null ) {
176
+ is GroupFormElement -> {
193
177
GroupElement (
194
- state,
178
+ state = entry.getState() ,
195
179
modifier = Modifier
196
180
.fillMaxWidth()
197
- .padding(
198
- start = 15 .dp,
199
- end = 15 .dp,
200
- top = 10 .dp,
201
- bottom = 10 .dp
202
- )
181
+ .padding(horizontal = 15 .dp, vertical = 10 .dp)
203
182
)
204
183
}
205
184
}
@@ -209,136 +188,142 @@ private fun FeatureFormBody(
209
188
}
210
189
}
211
190
191
+ /* *
192
+ * Creates and remembers state objects for all the supported element types that are part of the
193
+ * provided FeatureForm. These state objects are returned as part of a [FormStateCollection].
194
+ *
195
+ * @param form the [FeatureForm] to create the states for.
196
+ * @param context a [Context].
197
+ * @param scope a [CoroutineScope] to run collectors and calculations on.
198
+ *
199
+ * @return returns the [FormStateCollection] created.
200
+ */
212
201
@Composable
213
- internal fun rememberGroupStates (
202
+ internal fun rememberStates (
214
203
form : FeatureForm ,
215
204
context : Context ,
216
- scope : CoroutineScope ,
217
- ): Map <Int , BaseGroupState > {
218
- return form.elements.filterIsInstance<GroupFormElement >().associateBy(
219
- { groupElement ->
220
- groupElement.id
221
- },
222
- { groupElement ->
223
- val fieldStates = rememberFieldStates(
224
- form = form,
225
- elements = groupElement.formElements,
226
- context = context,
227
- scope = scope
228
- )
229
- rememberBaseGroupState(groupElement = groupElement, fieldStates = fieldStates)
205
+ scope : CoroutineScope
206
+ ): FormStateCollection {
207
+ val states = MutableStateCollection ()
208
+ form.elements.forEach { element ->
209
+ when (element) {
210
+ is FieldFormElement -> {
211
+ val state = rememberFieldState(element, form, context, scope)
212
+ if (state != null ) {
213
+ states.add(element, state)
214
+ }
215
+ }
216
+
217
+ is GroupFormElement -> {
218
+ val fieldStateCollection = MutableStateCollection ()
219
+ element.formElements.forEach {
220
+ if (it is FieldFormElement ) {
221
+ val state = rememberFieldState(
222
+ element = it,
223
+ form = form,
224
+ context = context,
225
+ scope = scope
226
+ )
227
+ if (state != null ) {
228
+ fieldStateCollection.add(it, state)
229
+ }
230
+ }
231
+ }
232
+ val groupState = rememberBaseGroupState(
233
+ groupElement = element,
234
+ fieldStates = fieldStateCollection
235
+ )
236
+ states.add(element, groupState)
237
+ }
230
238
}
231
- )
239
+ }
240
+ return states
232
241
}
233
242
243
+ /* *
244
+ * Creates and remembers a [BaseFieldState] for the provided [element].
245
+ *
246
+ * @param element the [FieldFormElement] to create the state for.
247
+ * @param form the [FeatureForm] the [element] is part of.
248
+ * @param context a [Context].
249
+ * @param scope a [CoroutineScope] to run collectors and calculations on.
250
+ *
251
+ * @return returns the [BaseFieldState] created.
252
+ */
234
253
@Composable
235
- internal fun rememberFieldStates (
254
+ internal fun rememberFieldState (
255
+ element : FieldFormElement ,
236
256
form : FeatureForm ,
237
- elements : List <FormElement >,
238
257
context : Context ,
239
258
scope : CoroutineScope
240
- ): Map <Int , BaseFieldState <* >> {
241
- val stateMap = mutableMapOf<Int , BaseFieldState <* >>()
242
- elements.forEach { element ->
243
- if (element is FieldFormElement ) {
244
- val state = when (element.input) {
245
- is TextBoxFormInput , is TextAreaFormInput -> {
246
- val minLength = if (element.input is TextBoxFormInput ) {
247
- (element.input as TextBoxFormInput ).minLength.toInt()
248
- } else {
249
- (element.input as TextAreaFormInput ).minLength.toInt()
250
- }
251
- val maxLength = if (element.input is TextBoxFormInput ) {
252
- (element.input as TextBoxFormInput ).maxLength.toInt()
253
- } else {
254
- (element.input as TextAreaFormInput ).maxLength.toInt()
255
- }
256
- rememberFormTextFieldState(
257
- field = element,
258
- minLength = minLength,
259
- maxLength = maxLength,
260
- form = form,
261
- context = context,
262
- scope = scope
263
- )
264
- }
259
+ ): BaseFieldState <out Any ?>? {
260
+ return when (element.input) {
261
+ is TextBoxFormInput , is TextAreaFormInput -> {
262
+ val minLength = if (element.input is TextBoxFormInput ) {
263
+ (element.input as TextBoxFormInput ).minLength.toInt()
264
+ } else {
265
+ (element.input as TextAreaFormInput ).minLength.toInt()
266
+ }
267
+ val maxLength = if (element.input is TextBoxFormInput ) {
268
+ (element.input as TextBoxFormInput ).maxLength.toInt()
269
+ } else {
270
+ (element.input as TextAreaFormInput ).maxLength.toInt()
271
+ }
272
+ rememberFormTextFieldState(
273
+ field = element,
274
+ minLength = minLength,
275
+ maxLength = maxLength,
276
+ form = form,
277
+ context = context,
278
+ scope = scope
279
+ )
280
+ }
265
281
266
- is DateTimePickerFormInput -> {
267
- val input = element.input as DateTimePickerFormInput
268
- rememberDateTimeFieldState(
269
- field = element,
270
- minEpochMillis = input.min,
271
- maxEpochMillis = input.max,
272
- shouldShowTime = input.includeTime,
273
- form = form,
274
- scope = scope
275
- )
276
- }
282
+ is DateTimePickerFormInput -> {
283
+ val input = element.input as DateTimePickerFormInput
284
+ rememberDateTimeFieldState(
285
+ field = element,
286
+ minEpochMillis = input.min,
287
+ maxEpochMillis = input.max,
288
+ shouldShowTime = input.includeTime,
289
+ form = form,
290
+ scope = scope
291
+ )
292
+ }
277
293
278
- is ComboBoxFormInput -> {
279
- rememberCodedValueFieldState(
280
- field = element,
281
- form = form,
282
- scope = scope
283
- )
284
- }
294
+ is ComboBoxFormInput -> {
295
+ rememberCodedValueFieldState(
296
+ field = element,
297
+ form = form,
298
+ scope = scope
299
+ )
300
+ }
285
301
286
- is SwitchFormInput -> {
287
- val input = element.input as SwitchFormInput
288
- val initialValue = element.formattedValue
289
- val fallback = initialValue.isEmpty()
290
- || (element.value.value != input.onValue.code && element.value.value != input.offValue.code)
291
- rememberSwitchFieldState(
292
- field = element,
293
- form = form,
294
- fallback = fallback,
295
- scope = scope,
296
- noValueString = context.getString(R .string.no_value)
297
- )
298
- }
302
+ is SwitchFormInput -> {
303
+ rememberSwitchFieldState(
304
+ field = element,
305
+ form = form,
306
+ scope = scope,
307
+ noValueString = context.getString(R .string.no_value)
308
+ )
309
+ }
299
310
300
- is RadioButtonsFormInput -> {
301
- rememberRadioButtonFieldState(
302
- field = element,
303
- form = form,
304
- scope = scope
305
- )
306
- }
311
+ is RadioButtonsFormInput -> {
312
+ rememberRadioButtonFieldState(
313
+ field = element,
314
+ form = form,
315
+ scope = scope
316
+ )
317
+ }
307
318
308
- else -> {
309
- null
310
- }
311
- }
312
- if (state != null ) {
313
- stateMap[element.id] = state
314
- }
319
+ else -> {
320
+ null
315
321
}
316
322
}
317
- return stateMap
318
323
}
319
324
320
325
@Preview(showBackground = true , backgroundColor = 0xFFFFFF )
321
326
@Composable
322
327
private fun InitializingExpressionsPreview () {
323
328
InitializingExpressions { false }
324
329
}
325
-
326
- /* *
327
- * Unique id for each form element.
328
- */
329
- internal val FieldFormElement .id: Int
330
- get() {
331
- return Objects .hash(fieldName, label, description, hint)
332
- }
333
-
334
- /* *
335
- * Unique id for each form element.
336
- */
337
- internal val GroupFormElement .id: Int
338
- get() {
339
- return Objects .hash(
340
- formElements.forEach { if (it is FieldFormElement ) it.id },
341
- label,
342
- description
343
- )
344
- }
0 commit comments