diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java index f0c83640940..3353b52e101 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java @@ -330,6 +330,7 @@ private FieldUiModel transform(@NonNull ProgramStageDataElement stage, DataEleme dataValue = option.get(0).displayName(); } optionSetConfig = new OptionSetConfiguration( + null, OptionSetConfiguration.Companion.optionDataFlow( d2.optionModule().options().byOptionSetUid().eq(optionSet).getPagingData(10), option1 -> metadataIconProvider.invoke(option1.style())) diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt index 5b9bad669c5..a3834fac2e3 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt @@ -314,7 +314,8 @@ class SearchRepositoryImplKt( val optionSetConfiguration = attribute.optionSet()?.let { OptionSetConfiguration( - d2.optionModule().options() + searchEmitter = null, + optionFlow = d2.optionModule().options() .byOptionSetUid().eq(attribute.optionSet()!!.uid()) .getPagingData(10) .map { pagingData -> @@ -357,7 +358,8 @@ class SearchRepositoryImplKt( val optionSetConfiguration = attribute.optionSet()?.let { OptionSetConfiguration( - d2.optionModule().options() + searchEmitter = null, + optionFlow = d2.optionModule().options() .byOptionSetUid().eq(attribute.optionSet()!!.uid()) .getPagingData(10) .map { pagingData -> diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt index 3abd9a615dc..1e9d3d3a746 100644 --- a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt @@ -1,9 +1,16 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + package org.dhis2.form.data import androidx.compose.ui.graphics.Color import androidx.paging.PagingData import androidx.paging.map +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import org.dhis2.commons.resources.MetadataIconProvider import org.dhis2.form.data.metadata.FormBaseConfiguration @@ -13,6 +20,7 @@ import org.dhis2.form.model.SectionUiModelImpl import org.dhis2.form.ui.FieldViewModelFactory import org.hisp.dhis.android.core.imports.TrackerImportConflict import org.hisp.dhis.android.core.program.SectionRenderingType +import timber.log.Timber abstract class DataEntryBaseRepository( private val conf: FormBaseConfiguration, @@ -69,25 +77,34 @@ abstract class DataEntryBaseRepository( override fun options( optionSetUid: String, - query: String, optionsToHide: List, optionGroupsToHide: List, optionGroupsToShow: List, - ): Flow> { - return conf.options( - optionSetUid, - query, - optionsToHide, - optionGroupsToHide, - optionGroupsToShow, - ).map { pagingData -> - pagingData.map { option -> - OptionSetConfiguration.OptionData( - option, - metadataIconProvider(option.style(), defaultStyleColor), - ) - } - } + ): Pair, Flow>> { + val searchFlow = MutableStateFlow("") + return Pair( + searchFlow, + searchFlow.debounce(300) + .flatMapLatest { query -> + conf.options( + optionSetUid, + query, + optionsToHide, + optionGroupsToHide, + optionGroupsToShow, + ).map { pagingData -> + pagingData.map { option -> + OptionSetConfiguration.OptionData( + option, + metadataIconProvider(option.style(), defaultStyleColor), + ) + } + } + } + .catch { + Timber.e(it) + }, + ) } override fun dateFormatConfiguration(): String? { diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt index dddb1f938c6..5572dfe725c 100644 --- a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt @@ -3,6 +3,7 @@ package org.dhis2.form.data import androidx.paging.PagingData import io.reactivex.Flowable import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import org.dhis2.form.model.EventMode import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.OptionSetConfiguration @@ -40,11 +41,11 @@ interface DataEntryRepository { fun disableCollapsableSections(): Boolean? fun getSpecificDataEntryItems(uid: String): List + fun options( optionSetUid: String, - query: String = "", optionsToHide: List, optionGroupsToHide: List, optionGroupsToShow: List, - ): Flow> + ): Pair, Flow>> } diff --git a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt index eb851c5eb67..ac11112c4bf 100644 --- a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt @@ -176,8 +176,15 @@ class EnrollmentRepository( var optionSetConfig: OptionSetConfiguration? = null if (!optionSet.isNullOrEmpty()) { + val (searchEmitter, optionFlow) = options( + optionSetUid = optionSet, + optionsToHide = emptyList(), + optionGroupsToHide = emptyList(), + optionGroupsToShow = emptyList(), + ) optionSetConfig = OptionSetConfiguration( - options(optionSet, "", emptyList(), emptyList(), emptyList()), + searchEmitter = searchEmitter, + optionFlow = optionFlow, ) } diff --git a/form/src/main/java/org/dhis2/form/data/EventRepository.kt b/form/src/main/java/org/dhis2/form/data/EventRepository.kt index 7169c63680e..f2a2204d4aa 100644 --- a/form/src/main/java/org/dhis2/form/data/EventRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EventRepository.kt @@ -441,7 +441,8 @@ class EventRepository( .withTrackedEntityType() .byUid().eq(programUid) .one().blockingGet()?.let { program -> - val firstAvailablePeriodDate = getFirstAvailablePeriod(event?.enrollment(), programStage) + val firstAvailablePeriodDate = + getFirstAvailablePeriod(event?.enrollment(), programStage) var minDate = dateUtils.expDate( firstAvailablePeriodDate, program.expiryDays() ?: 0, @@ -478,7 +479,11 @@ class EventRepository( } val calendar = DateUtils.getInstance().getCalendarByDate(minEventDate) - return dateUtils.getNextPeriod(programStage?.periodType(), calendar.time ?: event?.eventDate(), if (stageLastDate == null) 0 else 1) + return dateUtils.getNextPeriod( + programStage?.periodType(), + calendar.time ?: event?.eventDate(), + if (stageLastDate == null) 0 else 1, + ) } private fun getStageLastDate(): Date? { @@ -489,12 +494,14 @@ class EventRepository( .eq(enrollmentUid).byProgramStageUid() .eq(programStageUid) .byDeleted().isFalse - .orderByEventDate(RepositoryScope.OrderByDirection.DESC).blockingGet().filter { it.uid() != eventUid } + .orderByEventDate(RepositoryScope.OrderByDirection.DESC).blockingGet() + .filter { it.uid() != eventUid } val scheduleEvents = d2.eventModule().events().byEnrollmentUid().eq(enrollmentUid).byProgramStageUid() .eq(programStageUid) .byDeleted().isFalse - .orderByDueDate(RepositoryScope.OrderByDirection.DESC).blockingGet().filter { it.uid() != eventUid } + .orderByDueDate(RepositoryScope.OrderByDirection.DESC).blockingGet() + .filter { it.uid() != eventUid } var activeDate: Date? = null var scheduleDate: Date? = null @@ -633,15 +640,15 @@ class EventRepository( .byCode() .eq(dataValue).one().blockingGet()?.displayName() } - + val (searchEmitter, optionFlow) = options( + optionSetUid = optionSet!!, + optionsToHide = emptyList(), + optionGroupsToHide = emptyList(), + optionGroupsToShow = emptyList(), + ) optionSetConfig = OptionSetConfiguration( - optionFlow = options( - optionSetUid = optionSet!!, - "", - emptyList(), - emptyList(), - emptyList(), - ), + searchEmitter = searchEmitter, + optionFlow = optionFlow, ) } val fieldRendering = getValueTypeDeviceRendering(programStageDataElement) diff --git a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt index 5ce60922163..50fa81f84b2 100644 --- a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt +++ b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt @@ -801,18 +801,19 @@ class FormRepositoryImpl( } override fun fetchOptions(uid: String, optionSetUid: String) { - val flow = dataEntryRepository.options( - optionSetUid, - query = "", - ruleEffectsResult?.optionsToHide(uid) ?: emptyList(), - ruleEffectsResult?.optionGroupsToHide(uid) ?: emptyList(), - ruleEffectsResult?.optionGroupsToShow(uid) ?: emptyList(), + val (searchEmitter, flow) = dataEntryRepository.options( + optionSetUid = optionSetUid, + optionsToHide = ruleEffectsResult?.optionsToHide(uid) ?: emptyList(), + optionGroupsToHide = ruleEffectsResult?.optionGroupsToHide(uid) ?: emptyList(), + optionGroupsToShow = ruleEffectsResult?.optionGroupsToShow(uid) ?: emptyList(), ) - val newConf = OptionSetConfiguration(flow) + val newConf = OptionSetConfiguration( + searchEmitter = searchEmitter, + optionFlow = flow, + ) itemList.let { list -> - list.find { item -> item.uid == uid }?.let { item -> diff --git a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt index 26d05bc3634..801109e4efe 100644 --- a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt +++ b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt @@ -3,14 +3,15 @@ package org.dhis2.form.model import androidx.paging.PagingData import androidx.paging.map import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import org.dhis2.ui.MetadataIconData import org.hisp.dhis.android.core.option.Option data class OptionSetConfiguration( + val searchEmitter: MutableStateFlow? = null, val optionFlow: Flow>, ) { - companion object { fun optionDataFlow( flow: Flow>, @@ -26,11 +27,6 @@ data class OptionSetConfiguration( } } - data class OptionConfigData( - val options: List, - val metadataIconMap: Map, - ) - data class OptionData( val option: Option, val metadataIconData: MetadataIconData, diff --git a/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt b/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt index 63ed521934f..98d2c34343a 100644 --- a/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt +++ b/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt @@ -94,6 +94,5 @@ sealed class FormIntent { val uid: String, val optionSetUid: String, val value: String?, - val query: String, ) : FormIntent() } diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt index 9636b8d6a6e..4ac4073ac97 100644 --- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt +++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt @@ -1,12 +1,15 @@ package org.dhis2.form.ui.provider.inputfield import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.paging.compose.collectAsLazyPagingItems +import kotlinx.coroutines.launch import org.dhis2.form.extensions.inputState import org.dhis2.form.extensions.legend import org.dhis2.form.extensions.supportingText @@ -20,13 +23,28 @@ fun ProvideDropdownInput( modifier: Modifier, inputStyle: InputStyle, fieldUiModel: FieldUiModel, - fetchOptions: (query: String) -> Unit, + fetchOptions: () -> Unit, ) { var selectedItem by remember(fieldUiModel) { mutableStateOf(DropdownItem(fieldUiModel.displayName ?: "")) } - val optionsData = fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems() + val optionSetConfiguration by remember(fieldUiModel) { + mutableStateOf(fieldUiModel.optionSetConfiguration) + } + + val optionsData = optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems() + + val useDropdown by remember { + derivedStateOf { + optionSetConfiguration?.searchEmitter?.value?.isEmpty() == true && ( + optionsData?.itemCount + ?: 0 + ) < 15 + } + } + + val scope = rememberCoroutineScope() InputDropDown( modifier = modifier, @@ -42,18 +60,19 @@ fun ProvideDropdownInput( DropdownItem(optionsData?.get(index)?.option?.displayName() ?: "") }, onSearchOption = { query -> - fetchOptions(query) + scope.launch { fieldUiModel.optionSetConfiguration?.searchEmitter?.emit(query) } }, itemCount = optionsData?.itemCount ?: 0, - useDropDown = (optionsData?.itemCount ?: 0) < 15, + useDropDown = useDropdown, onItemSelected = { index, newSelectedItem -> selectedItem = newSelectedItem fieldUiModel.onSave( optionsData?.get(index)?.option?.code(), ) }, - loadOptions = { - fetchOptions("") + loadOptions = fetchOptions, + onDismiss = { + scope.launch { fieldUiModel.optionSetConfiguration?.searchEmitter?.emit("") } }, ) } 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 f857a69ab4b..7b415964efb 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 @@ -113,13 +113,12 @@ fun FieldProvider( fieldUiModel = fieldUiModel, intentHandler = intentHandler, context = context, - fetchOptions = { query -> + fetchOptions = { intentHandler( FormIntent.FetchOptions( fieldUiModel.uid, fieldUiModel.optionSet!!, value = fieldUiModel.value, - query = query, ), ) }, @@ -486,7 +485,7 @@ fun ProvideByOptionSet( fieldUiModel: FieldUiModel, intentHandler: (FormIntent) -> Unit, context: Context, - fetchOptions: (query: String) -> Unit, + fetchOptions: () -> Unit, ) { when (fieldUiModel.renderingType) { UiRenderType.HORIZONTAL_RADIOBUTTONS, diff --git a/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt index 243e8d4a405..09e262b6a71 100644 --- a/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt +++ b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt @@ -6,6 +6,7 @@ import io.reactivex.Flowable import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -365,9 +366,8 @@ class ProgramRulesTest { any(), any(), any(), - any(), ), - )doReturn emptyFlow() + ) doReturn Pair(MutableStateFlow(""), emptyFlow()) val intent = FormIntent.OnSave( uid = "uid004", @@ -385,7 +385,6 @@ class ProgramRulesTest { verify(dataEntryRepository).options( optionSetUid = "optionSetUid", - query = "", optionsToHide = emptyList(), optionGroupsToHide = emptyList(), optionGroupsToShow = listOf("optionGroupId"), @@ -417,9 +416,8 @@ class ProgramRulesTest { any(), any(), any(), - any(), ), - )doReturn emptyFlow() + ) doReturn Pair(MutableStateFlow(""), emptyFlow()) val intent = FormIntent.OnSave( uid = "uid004", @@ -437,7 +435,6 @@ class ProgramRulesTest { verify(dataEntryRepository).options( optionSetUid = "optionSetUid", - query = "", optionsToHide = listOf("Option2"), optionGroupsToHide = emptyList(), optionGroupsToShow = emptyList(), @@ -506,6 +503,7 @@ class ProgramRulesTest { label = "field6", valueType = ValueType.MULTI_TEXT, optionSetConfiguration = OptionSetConfiguration( + MutableStateFlow(""), emptyFlow(), ), autocompleteList = null, @@ -517,7 +515,10 @@ class ProgramRulesTest { value = "value07", label = "field7", valueType = ValueType.MULTI_TEXT, - optionSetConfiguration = OptionSetConfiguration(emptyFlow()), + optionSetConfiguration = OptionSetConfiguration( + MutableStateFlow(""), + emptyFlow(), + ), autocompleteList = null, programStageSection = "section2", optionSet = "optionSetUid",