From 68db6e677acf667be12f5867a38c7803d88d03e0 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 28 Feb 2024 16:19:12 +0100 Subject: [PATCH] [ANDROAPP-1540] Multi selection for option set in programs (#3500) Signed-off-by: Pablo --- .../development/DevelopmentActivity.java | 19 +++++++ .../main/res/layout/development_activity.xml | 26 +++++++++ .../dhis2/commons/bindings/ValueExtensions.kt | 7 ++- .../form/ui/FieldViewModelFactoryImpl.kt | 4 +- .../ui/provider/inputfield/FieldProvider.kt | 9 +++- .../inputfield/MultiSelectionInputProvider.kt | 54 +++++++++++++++++++ 6 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt diff --git a/app/src/main/java/org/dhis2/usescases/development/DevelopmentActivity.java b/app/src/main/java/org/dhis2/usescases/development/DevelopmentActivity.java index 5bcf549f3a..3c8add8f6e 100644 --- a/app/src/main/java/org/dhis2/usescases/development/DevelopmentActivity.java +++ b/app/src/main/java/org/dhis2/usescases/development/DevelopmentActivity.java @@ -14,6 +14,7 @@ import org.dhis2.usescases.general.ActivityGlobalAbstract; import org.hisp.dhis.android.core.D2; import org.hisp.dhis.android.core.D2Manager; +import org.hisp.dhis.android.core.common.ValueType; import java.io.BufferedReader; import java.io.IOException; @@ -39,6 +40,24 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { loadCrashControl(); loadFeatureConfig(); loadConflicts(); + loadMultiText(); + } + + private void loadMultiText() { + D2 d2 = D2Manager.getD2(); + boolean hasMultiText = !d2.dataElementModule().dataElements().byValueType().eq(ValueType.MULTI_TEXT).blockingIsEmpty(); + binding.multitext.setText(hasMultiText ? "REVERT" : "FORCE MULTITEXT"); + binding.multitext.setOnClickListener(view -> { + if (hasMultiText) { + d2.databaseAdapter().execSQL( + "UPDATE DataElement SET valueType = \"TEXT\" WHERE valueType = \"MULTI_TEXT\" AND optionSet IS NOT null" + ); + } else { + d2.databaseAdapter().execSQL( + "UPDATE DataElement SET valueType = \"MULTI_TEXT\" WHERE valueType = \"TEXT\" AND optionSet IS NOT null" + ); + } + }); } private void loadConflicts() { diff --git a/app/src/main/res/layout/development_activity.xml b/app/src/main/res/layout/development_activity.xml index a7a57ce5cc..212ff2bad1 100644 --- a/app/src/main/res/layout/development_activity.xml +++ b/app/src/main/res/layout/development_activity.xml @@ -185,6 +185,32 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:text="UI Components" /> + + + + + + + diff --git a/commons/src/main/java/org/dhis2/commons/bindings/ValueExtensions.kt b/commons/src/main/java/org/dhis2/commons/bindings/ValueExtensions.kt index 2c067401b4..85c0602f01 100644 --- a/commons/src/main/java/org/dhis2/commons/bindings/ValueExtensions.kt +++ b/commons/src/main/java/org/dhis2/commons/bindings/ValueExtensions.kt @@ -22,7 +22,7 @@ fun TrackedEntityAttributeValue.userFriendlyValue(d2: D2): String? { } if (check(d2, attribute.valueType(), attribute.optionSet()?.uid(), value()!!)) { - attribute.optionSet()?.let { + attribute.optionSet()?.takeIf { attribute.valueType() != ValueType.MULTI_TEXT }?.let { return checkOptionSetValue(d2, it.uid(), value()!!) } ?: return checkValueTypeValue(d2, attribute.valueType(), value()!!) } else { @@ -45,7 +45,7 @@ fun TrackedEntityDataValue?.userFriendlyValue(d2: D2): String? { if (dataElement == null) { return null } else if (check(d2, dataElement.valueType(), dataElement.optionSet()?.uid(), value()!!)) { - dataElement.optionSet()?.let { + dataElement.optionSet()?.takeIf { dataElement.valueType() != ValueType.MULTI_TEXT }?.let { return checkOptionSetValue(d2, it.uid(), value()!!) } ?: return checkValueTypeValue(d2, dataElement.valueType(), value()!!) } else { @@ -191,14 +191,13 @@ fun TrackedEntityDataValueObjectRepository.blockingGetValueCheck( private fun check(d2: D2, valueType: ValueType?, optionSetUid: String?, value: String): Boolean { return when { - optionSetUid != null -> { + valueType != ValueType.MULTI_TEXT && optionSetUid != null -> { val optionByCodeExist = d2.optionModule().options().byOptionSetUid().eq(optionSetUid) .byCode().eq(value).one().blockingExists() val optionByNameExist = d2.optionModule().options().byOptionSetUid().eq(optionSetUid) .byDisplayName().eq(value).one().blockingExists() optionByCodeExist || optionByNameExist } - valueType != null -> { if (valueType.isNumeric) { try { diff --git a/form/src/main/java/org/dhis2/form/ui/FieldViewModelFactoryImpl.kt b/form/src/main/java/org/dhis2/form/ui/FieldViewModelFactoryImpl.kt index aefb3f6239..0fd5c6ef65 100644 --- a/form/src/main/java/org/dhis2/form/ui/FieldViewModelFactoryImpl.kt +++ b/form/src/main/java/org/dhis2/form/ui/FieldViewModelFactoryImpl.kt @@ -74,9 +74,9 @@ class FieldViewModelFactoryImpl( style = uiStyleProvider.provideStyle(valueType), hint = hintProvider.provideDateHint(valueType), description = description, - valueType = valueType, + valueType = if (optionSet != null && valueType == ValueType.TEXT) ValueType.MULTI_TEXT else valueType, legend = legendValueProvider.provideLegendValue(id, value), - optionSet = optionSet, + optionSet = if (valueType == ValueType.MULTI_TEXT) null else optionSet, allowFutureDates = allowFutureDates, uiEventFactory = UiEventFactoryImpl( id, diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt index ef826132b7..4a03971071 100644 --- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt +++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt @@ -398,11 +398,18 @@ fun FieldProvider( ) } + ValueType.MULTI_TEXT -> { + ProvideMultiSelectionInput( + modifier = modifierWithFocus, + fieldUiModel = fieldUiModel, + intentHandler = intentHandler, + ) + } + ValueType.REFERENCE, ValueType.GEOJSON, ValueType.USERNAME, ValueType.TRACKER_ASSOCIATE, - ValueType.MULTI_TEXT, null, -> { InputNotSupported(title = fieldUiModel.label) diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt new file mode 100644 index 0000000000..02da27dbe9 --- /dev/null +++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt @@ -0,0 +1,54 @@ +package org.dhis2.form.ui.provider.inputfield + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import org.dhis2.form.extensions.inputState +import org.dhis2.form.extensions.legend +import org.dhis2.form.extensions.supportingText +import org.dhis2.form.model.FieldUiModel +import org.dhis2.form.ui.intent.FormIntent +import org.hisp.dhis.mobile.ui.designsystem.component.CheckBoxData +import org.hisp.dhis.mobile.ui.designsystem.component.InputMultiSelection + +@Composable +internal fun ProvideMultiSelectionInput( + modifier: Modifier, + fieldUiModel: FieldUiModel, + intentHandler: (FormIntent) -> Unit, +) { + val optionsToDisplay = fieldUiModel.optionSetConfiguration?.optionsToDisplay() ?: emptyList() + val data = optionsToDisplay.map { option -> + CheckBoxData( + uid = option.uid(), + checked = option.code()?.let { fieldUiModel.value?.contains(it) } ?: false, + enabled = true, + textInput = option.displayName() ?: "", + ) + } + + InputMultiSelection( + modifier = modifier, + title = fieldUiModel.label, + items = data, + state = fieldUiModel.inputState(), + supportingTextData = fieldUiModel.supportingText(), + legendData = fieldUiModel.legend(), + isRequired = fieldUiModel.mandatory, + onItemsSelected = { + val checkedValues = it.filter { item -> item.checked }.mapNotNull { + optionsToDisplay.find { option -> option.uid() == it.uid }?.code() + } + + intentHandler( + FormIntent.OnSave( + fieldUiModel.uid, + checkedValues.joinToString(separator = ","), + fieldUiModel.valueType, + ), + ) + }, + onClearItemSelection = { + intentHandler(FormIntent.ClearValue(fieldUiModel.uid)) + }, + ) +}