From eadef6d9a8f76139f39cf2027525843c9775d3aa Mon Sep 17 00:00:00 2001 From: Christopher Seven Phiri Date: Wed, 12 Jun 2024 11:22:40 +0200 Subject: [PATCH 1/3] update care plan task state issues --- .../data/local/register/dao/HivRegisterDao.kt | 41 ++++++++++++++++--- .../engine/task/FhirCarePlanGenerator.kt | 17 +------- .../engine/util/extension/TaskExtension.kt | 14 +++++++ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/dao/HivRegisterDao.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/dao/HivRegisterDao.kt index 30b999c195..4d936ed749 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/dao/HivRegisterDao.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/dao/HivRegisterDao.kt @@ -31,6 +31,7 @@ import javax.inject.Provider import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import org.hl7.fhir.r4.model.CarePlan import org.hl7.fhir.r4.model.CodeType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -42,6 +43,7 @@ import org.hl7.fhir.r4.model.Reference import org.hl7.fhir.r4.model.RelatedPerson import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType +import org.hl7.fhir.r4.model.Task import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.data.domain.Guardian import org.smartregister.fhircore.engine.data.domain.PregnancyStatus @@ -74,6 +76,7 @@ import org.smartregister.fhircore.engine.util.extension.getResourcesByIds import org.smartregister.fhircore.engine.util.extension.givenName import org.smartregister.fhircore.engine.util.extension.loadResource import org.smartregister.fhircore.engine.util.extension.shouldShowOnProfile +import org.smartregister.fhircore.engine.util.extension.taskStatusToCarePlanActivityStatus import org.smartregister.fhircore.engine.util.extension.toAgeDisplay import org.smartregister.fhircore.engine.util.extension.yearsPassed import timber.log.Timber @@ -243,12 +246,7 @@ constructor( showIdentifierInProfile = true, currentCarePlan = carePlan, healthStatus = patient.extractHealthStatusFromMeta(patientTypeMetaTagCodingSystem), - tasks = - carePlan - ?.activity - ?.filter { it.shouldShowOnProfile() } - ?.sortedWith(compareBy(nullsLast()) { it?.detail?.code?.text?.toBigIntegerOrNull() }) - ?: listOf(), + tasks = fetchCarePlanActivities(carePlan), conditions = defaultRepository.activePatientConditions(patient.logicalId), otherPatients = patient.otherChildren(), guardians = patient.guardians(), @@ -490,6 +488,37 @@ constructor( .filterNot { it.healthStatus == HealthStatus.DEFAULT } } + private suspend fun fetchCarePlanActivities( + carePlan: CarePlan? + ): List { + if (carePlan == null) return emptyList() + val activityOnList = mutableMapOf() + val tasksToFetch = mutableListOf() + for (planActivity in carePlan.activity) { + if (!planActivity.shouldShowOnProfile()) { + continue + } + val taskId = planActivity.outcomeReference.firstOrNull()?.extractId() + if (taskId != null) { + tasksToFetch.add(taskId) + activityOnList[taskId] = planActivity + } + } + if (tasksToFetch.isNotEmpty()) { + val tasks = fhirEngine.getResourcesByIds(tasksToFetch) + tasks.forEach { task -> + val planActivity: CarePlan.CarePlanActivityComponent? = activityOnList[task.logicalId] + if (planActivity != null) { + planActivity.detail?.status = task.taskStatusToCarePlanActivityStatus() + activityOnList[task.logicalId] = planActivity + } + } + } + return activityOnList.values.sortedWith( + compareBy(nullsLast()) { it.detail?.code?.text?.toBigIntegerOrNull() }, + ) + } + object ResourceValue { const val BLANK = "" } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt index 8dd1615ba7..21d5365c1b 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt @@ -36,6 +36,7 @@ import org.hl7.fhir.r4.utils.StructureMapUtilities import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.util.extension.encodeResourceToString import org.smartregister.fhircore.engine.util.extension.getCarePlanId +import org.smartregister.fhircore.engine.util.extension.taskStatusToCarePlanActivityStatus import org.smartregister.fhircore.engine.util.helper.TransformSupportServices import timber.log.Timber @@ -148,7 +149,7 @@ constructor(val fhirEngine: FhirEngine, val transformSupportServices: TransformS for ((index, value) in carePlan.activity.withIndex()) { val outcome = value.outcomeReference.find { x -> x.reference.contains(id) } if (outcome != null) { - value.detail.status = taskStatusToCarePlanActivityStatus(task.status) + value.detail.status = task.taskStatusToCarePlanActivityStatus() carePlan.activity?.set(index, value) break } @@ -176,18 +177,4 @@ constructor(val fhirEngine: FhirEngine, val transformSupportServices: TransformS else -> Task.TaskStatus.COMPLETED } } - - private fun taskStatusToCarePlanActivityStatus( - status: Task.TaskStatus, - ): CarePlan.CarePlanActivityStatus { - return when (status) { - Task.TaskStatus.FAILED -> CarePlan.CarePlanActivityStatus.STOPPED - Task.TaskStatus.CANCELLED -> CarePlan.CarePlanActivityStatus.CANCELLED - Task.TaskStatus.COMPLETED, - Task.TaskStatus.ONHOLD, - Task.TaskStatus.INPROGRESS, - Task.TaskStatus.ENTEREDINERROR, -> CarePlan.CarePlanActivityStatus.fromCode(status.toCode()) - else -> CarePlan.CarePlanActivityStatus.NULL - } - } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/TaskExtension.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/TaskExtension.kt index 105d9afc09..896b96a3a4 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/TaskExtension.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/TaskExtension.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.engine.util.extension +import org.hl7.fhir.r4.model.CarePlan import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.Task import org.smartregister.fhircore.engine.util.DateUtils @@ -71,3 +72,16 @@ fun Task.getCarePlanId(): String? { ?.code ?.substringAfterLast(delimiter = '/', missingDelimiterValue = "") } + +fun Task.taskStatusToCarePlanActivityStatus(): CarePlan.CarePlanActivityStatus { + return when (status) { + Task.TaskStatus.FAILED -> CarePlan.CarePlanActivityStatus.STOPPED + Task.TaskStatus.CANCELLED -> CarePlan.CarePlanActivityStatus.CANCELLED + Task.TaskStatus.READY -> CarePlan.CarePlanActivityStatus.NOTSTARTED + Task.TaskStatus.COMPLETED, + Task.TaskStatus.ONHOLD, + Task.TaskStatus.INPROGRESS, + Task.TaskStatus.ENTEREDINERROR, -> CarePlan.CarePlanActivityStatus.fromCode(status.toCode()) + else -> CarePlan.CarePlanActivityStatus.NULL + } +} \ No newline at end of file From bec254b293a4bbfba3c148556314385aa8d40dc3 Mon Sep 17 00:00:00 2001 From: Christopher Seven Phiri Date: Thu, 13 Jun 2024 10:01:12 +0200 Subject: [PATCH 2/3] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 80acc54a959629b5304b52c5b1aa6bdd1c3cdbf4 Author: Christopher Seven Phiri Date: Thu Jun 13 08:11:12 2024 +0200 Bump update commit 7fc007e8adea00aa21323eb3d01a0a497f4cc9ac Merge: 92ead081e 1ab21e4b0 Author: Christopher Seven Phiri Date: Thu Jun 13 08:10:48 2024 +0200 Merge branch 'perf-improvements' into pre-production-changes commit 92ead081e3faf113d2547fc6d167368c45095c2f Author: Christopher Seven Phiri Date: Wed Jun 12 23:15:58 2024 +0200 Bump versions commit 1ab21e4b0a418addacd82b90a5cb719794b2f337 Author: L≡ZRS <12814349+LZRS@users.noreply.github.com> Date: Thu Jun 13 02:13:51 2024 +0300 Revert alternating uploadStrategy due errors in ID assignment UploadStrategy SingleResourcePost had ID assigned on POST to server which would differ with ID assigned within the app, before sync commit 9e0c0eebc56e6f56a7141874ef9eeaf956772a5d Author: Christopher Seven Phiri Date: Wed Jun 12 23:10:42 2024 +0200 filter only art commit c00d1d14175f829736e45b0f12c7e8f868ffbf33 Author: Christopher Seven Phiri Date: Wed Jun 12 22:16:09 2024 +0200 create location picker --- .../fhircore/engine/sync/AppSyncWorker.kt | 5 +- ...olderFactoryMatchersProviderFactoryImpl.kt | 17 ++ .../items/CustomQuestItemDataProvider.kt | 71 +++++++ .../items/location/LocationPickerView.kt | 1 - .../items/patient/PatientPicker.kt | 178 ++++++++++++++++++ .../patient/PatientPickerViewHolderFactory.kt | 89 +++++++++ .../items/patient/PatientPickerViewModel.kt | 48 +++++ .../res/layout/custom_patient_picker_item.xml | 41 ++++ .../custom_quest_patient_picker_dialog.xml | 67 +++++++ .../custom_quest_patient_picker_item.xml | 80 ++++++++ .../engine/src/main/res/values/strings.xml | 2 + android/quest/build.gradle.kts | 8 +- 12 files changed, 598 insertions(+), 9 deletions(-) create mode 100644 android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPicker.kt create mode 100644 android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewHolderFactory.kt create mode 100644 android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewModel.kt create mode 100644 android/engine/src/main/res/layout/custom_patient_picker_item.xml create mode 100644 android/engine/src/main/res/layout/custom_quest_patient_picker_dialog.xml create mode 100644 android/engine/src/main/res/layout/custom_quest_patient_picker_item.xml diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt index bf951b59b0..5cc7a089ae 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt @@ -67,10 +67,7 @@ constructor( return withContext(dispatcherProvider.singleThread()) { super.doWork() } } - override fun getUploadStrategy(): UploadStrategy = - if (runAttemptCount % 2 == 0) { - UploadStrategy.AllChangesSquashedBundlePut - } else UploadStrategy.SingleResourcePost + override fun getUploadStrategy(): UploadStrategy = UploadStrategy.AllChangesSquashedBundlePut override fun getFhirEngine(): FhirEngine = engine } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl.kt index 7dc1d0bb83..47a1e0867a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl.kt @@ -22,6 +22,7 @@ import com.google.android.fhir.datacapture.contrib.views.barcode.BarCodeReaderVi import com.google.android.fhir.datacapture.extensions.asStringValue import org.smartregister.fhircore.engine.ui.questionnaire.items.CustomQuestItemDataProvider import org.smartregister.fhircore.engine.ui.questionnaire.items.LocationPickerViewHolderFactory +import org.smartregister.fhircore.engine.ui.questionnaire.items.patient.PatientPickerViewHolderFactory class QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl( private val customQuestItemDataProvider: CustomQuestItemDataProvider, @@ -65,6 +66,22 @@ class QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl( ) } }, + QuestionnaireFragment.QuestionnaireItemViewHolderFactoryMatcher( + PatientPickerViewHolderFactory( + customQuestItemDataProvider = customQuestItemDataProvider, + ), + ) { questionnaireItem -> + questionnaireItem.getExtensionByUrl(PatientPickerViewHolderFactory.WIDGET_EXTENSION).let { + if (it == null) { + false + } else + it.value.asStringValue() in + listOf( + PatientPickerViewHolderFactory.WIDGET_TYPE_GUARDIAN, + PatientPickerViewHolderFactory.WIDGET_TYPE_ALL, + ) + } + }, ) } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/CustomQuestItemDataProvider.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/CustomQuestItemDataProvider.kt index c095b92416..51a27fb0b8 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/CustomQuestItemDataProvider.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/CustomQuestItemDataProvider.kt @@ -16,16 +16,32 @@ package org.smartregister.fhircore.engine.ui.questionnaire.items +import ca.uhn.fhir.rest.gclient.TokenClientParam +import ca.uhn.fhir.rest.param.ParamPrefixEnum import com.google.android.fhir.FhirEngine import com.google.android.fhir.get +import com.google.android.fhir.search.Operation +import com.google.android.fhir.search.StringFilterModifier +import com.google.android.fhir.search.filter.TokenParamFilterCriterion +import com.google.android.fhir.search.search import com.google.gson.Gson import com.google.gson.reflect.TypeToken import javax.inject.Inject import org.hl7.fhir.r4.model.Binary +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.DateTimeType +import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.Reference +import org.smartregister.fhircore.engine.domain.model.HealthStatus import org.smartregister.fhircore.engine.domain.model.LocationHierarchy import org.smartregister.fhircore.engine.util.SharedPreferenceKey import org.smartregister.fhircore.engine.util.SharedPreferencesHelper import org.smartregister.fhircore.engine.util.SystemConstants +import org.smartregister.fhircore.engine.util.extension.asReference +import org.smartregister.fhircore.engine.util.extension.extractAge +import org.smartregister.fhircore.engine.util.extension.extractName +import org.smartregister.fhircore.engine.util.extension.extractOfficialIdentifier +import org.smartregister.fhircore.engine.util.extension.plusYears import timber.log.Timber class CustomQuestItemDataProvider @@ -59,4 +75,59 @@ constructor( listOf() } } + + suspend fun searchPatients(query: String): List { + val codings = listOf(HealthStatus.NEWLY_DIAGNOSED_CLIENT, HealthStatus.CLIENT_ALREADY_ON_ART).map { Coding().apply { + system = SystemConstants.PATIENT_TYPE_FILTER_TAG_VIA_META_CODINGS_SYSTEM + code = it.name.lowercase().replace("_", "-") + } }.map Unit> { c-> + {value = of(c)} + } + val patients = + fhirEngine.search { + filter(Patient.ACTIVE, { value = of(true) }) + filter(Patient.DECEASED, { value = of(false) }) + filter( + Patient.GENDER, + { value = of("male") }, + { value = of("female") }, + operation = Operation.OR, + ) + filter(TokenClientParam("_tag"), *codings.toTypedArray(), operation = Operation.OR) + if (query.contains(Regex("[0-9]"))) { + filter(Patient.IDENTIFIER, { value = of(query) }) + } else { + filter( + Patient.NAME, + { + modifier = StringFilterModifier.CONTAINS + value = query + }, + ) + } + count = 5 + } + + return patients.map { + val ref = it.resource.asReference() + val name = it.resource.extractName() + ref.display = name + PickerPatient( + name = name, + id = it.resource.extractOfficialIdentifier(), + gender = it.resource.gender.name, + age = it.resource.extractAge(), + reference = ref, + ) + } + } } + +data class PickerPatient( + val name: String, + val id: String?, + val gender: String, + val age: String, + val reference: Reference, +) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/location/LocationPickerView.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/location/LocationPickerView.kt index 60fd753024..c7c46e57ca 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/location/LocationPickerView.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/location/LocationPickerView.kt @@ -216,7 +216,6 @@ class LocationPickerView( } fun initLocation(initialAnswer: String?) { - Timber.e("$initialAnswer $initialValue") if (initialAnswer != null && initialValue == null) { val elements = initialAnswer.split("|") val locationId = elements.getOrNull(0) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPicker.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPicker.kt new file mode 100644 index 0000000000..412a2607b8 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPicker.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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 org.smartregister.fhircore.engine.ui.questionnaire.items.patient + +import android.app.AlertDialog +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.cardview.widget.CardView +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.fhir.datacapture.views.HeaderView +import com.google.android.material.card.MaterialCardView +import org.hl7.fhir.r4.model.Reference +import org.smartregister.fhircore.engine.R +import org.smartregister.fhircore.engine.databinding.CustomQuestPatientPickerDialogBinding +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.ui.questionnaire.items.CustomQuestItemDataProvider +import org.smartregister.fhircore.engine.ui.questionnaire.items.PickerPatient + +class PatientPicker( + private val context: AppCompatActivity, + itemView: View, + lifecycleScope: LifecycleCoroutineScope, + customQuestItemDataProvider: CustomQuestItemDataProvider, +) { + private var patientText: TextView? + var headerView: HeaderView? = null + private var cardView: CardView? = null + + private val helperText: TextView + private var errorView: LinearLayout + private var errorText: TextView + + private val viewModel: PatientPickerViewModel + + private var selectedPatient: Reference? = null + private var initialValue: Reference? = null + + private var onPatientChanged: ((Reference?) -> Unit)? = null + + init { + cardView = itemView.findViewById(R.id.patient_picker_container) + patientText = itemView.findViewById(R.id.patient_title_text) + helperText = itemView.findViewById(R.id.location_helper_text) + errorView = itemView.findViewById(R.id.item_error_view) + errorText = itemView.findViewById(R.id.error_text) + headerView = itemView.findViewById(R.id.header) + + cardView?.setOnClickListener { showDropdownDialog() } + + viewModel = PatientPickerViewModel(lifecycleScope, customQuestItemDataProvider) + } + + private fun showDropdownDialog() { + val dialogBinding = + CustomQuestPatientPickerDialogBinding.inflate(LayoutInflater.from(context), null, false) + val dialog = AlertDialog.Builder(context).setView(dialogBinding.root).create() + dialogBinding.submitButton.setOnClickListener { + val inputText = dialogBinding.patientSearchEditText.text.toString() + viewModel.submitText(inputText) + } + + viewModel.state.observe(context) { state -> + val isLoading = state is DataLoadState.Loading + + dialogBinding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE + dialogBinding.recyclerView.visibility = if (!isLoading) View.VISIBLE else View.GONE + dialogBinding.emptyStateTextView.visibility = View.GONE + dialogBinding.errorStateTextView.visibility = + if (state is DataLoadState.Error) View.VISIBLE else View.GONE + + if (state is DataLoadState.Success) { + dialogBinding.recyclerView.layoutManager = LinearLayoutManager(context) + dialogBinding.recyclerView.adapter = + ItemsAdapter(state.data) { + selectedPatient = it.reference + callOnPatientChange(it) + dialog.dismiss() + } + + dialogBinding.recyclerView.visibility = + if (state.data.isNotEmpty()) View.VISIBLE else View.GONE + dialogBinding.emptyStateTextView.visibility = + if (state.data.isEmpty()) View.VISIBLE else View.GONE + } + } + dialog.show() + } + + private fun callOnPatientChange(patientPicker: PickerPatient?) { + onPatientChanged?.invoke(selectedPatient) + patientText?.text = patientPicker?.name + } + + fun setOnPatientChanged(listener: ((Reference?) -> Unit)?) { + onPatientChanged = listener + } + + fun setRequiredOrOptionalText(requiredOrOptionalText: String?) { + helperText.text = requiredOrOptionalText + helperText.visibility = View.VISIBLE + } + + fun setEnabled(enabled: Boolean) { + cardView?.isEnabled = enabled + } + + fun initAnswer(initialAnswer: Reference?) { + if (initialAnswer != null && initialValue == null) { + selectedPatient = initialAnswer + patientText?.text = initialAnswer.display + initialValue = initialAnswer + } + } + + fun showError(validationErrorMessage: String?) { + if (validationErrorMessage == null) { + errorView.visibility = View.GONE + return + } + + errorView.visibility = View.VISIBLE + errorText.text = validationErrorMessage + } +} + +class ItemsAdapter( + private val items: List, + val onPatientSelected: (PickerPatient) -> Unit, +) : RecyclerView.Adapter() { + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val nameTextView: TextView = itemView.findViewById(R.id.nameTextView) + val idTextView: TextView = itemView.findViewById(R.id.idTextView) + val genderTextView: TextView = itemView.findViewById(R.id.genderTextView) + val ageTextView: TextView = itemView.findViewById(R.id.ageTextView) + val patientContainer: MaterialCardView = itemView.findViewById(R.id.patient_container) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = + LayoutInflater.from(parent.context) + .inflate(R.layout.custom_patient_picker_item, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val person = items[position] + holder.nameTextView.text = person.name + holder.idTextView.text = person.id + holder.genderTextView.text = person.gender + holder.ageTextView.text = person.age + holder.patientContainer.setOnClickListener { onPatientSelected(person) } + } + + override fun getItemCount(): Int { + return items.size + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewHolderFactory.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewHolderFactory.kt new file mode 100644 index 0000000000..8d9f52086a --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewHolderFactory.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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 org.smartregister.fhircore.engine.ui.questionnaire.items.patient + +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.google.android.fhir.datacapture.extensions.getRequiredOrOptionalText +import com.google.android.fhir.datacapture.extensions.getValidationErrorMessage +import com.google.android.fhir.datacapture.extensions.tryUnwrapContext +import com.google.android.fhir.datacapture.views.QuestionnaireViewItem +import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderDelegate +import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderFactory +import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.smartregister.fhircore.engine.R +import org.smartregister.fhircore.engine.ui.questionnaire.items.CustomQuestItemDataProvider + +class PatientPickerViewHolderFactory(val customQuestItemDataProvider: CustomQuestItemDataProvider) : + QuestionnaireItemViewHolderFactory(R.layout.custom_quest_patient_picker_item) { + private lateinit var context: AppCompatActivity + + override fun getQuestionnaireItemViewHolderDelegate(): QuestionnaireItemViewHolderDelegate = + object : QuestionnaireItemViewHolderDelegate { + lateinit var patientPickerView: PatientPicker + + override lateinit var questionnaireViewItem: QuestionnaireViewItem + + override fun bind(questionnaireViewItem: QuestionnaireViewItem) { + patientPickerView.headerView?.bind(questionnaireViewItem) + patientPickerView.setRequiredOrOptionalText( + getRequiredOrOptionalText(questionnaireViewItem, context), + ) + patientPickerView.setOnPatientChanged { value -> + context.lifecycleScope.launch { + if (value != null) { + questionnaireViewItem.setAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(value), + ) + } else { + questionnaireViewItem.clearAnswer() + } + } + } + + val initialAnswer = questionnaireViewItem.answers.singleOrNull()?.valueReference + patientPickerView.initAnswer(initialAnswer) + if (questionnaireViewItem.draftAnswer == null) { + patientPickerView.showError( + getValidationErrorMessage( + context, + questionnaireViewItem, + questionnaireViewItem.validationResult, + ), + ) + } + } + + override fun init(itemView: View) { + context = itemView.context.tryUnwrapContext()!! + patientPickerView = + PatientPicker(context, itemView, context.lifecycleScope, customQuestItemDataProvider) + } + + override fun setReadOnly(isReadOnly: Boolean) { + patientPickerView.setEnabled(!isReadOnly) + } + } + + companion object { + const val WIDGET_EXTENSION = "https://d-tree.org/fhir/extensions/patient-widget" + const val WIDGET_TYPE_GUARDIAN = "patient-guardian" + const val WIDGET_TYPE_ALL = "patient-all" + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewModel.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewModel.kt new file mode 100644 index 0000000000..e012358818 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/items/patient/PatientPickerViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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 org.smartregister.fhircore.engine.ui.questionnaire.items.patient + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.ui.questionnaire.items.CustomQuestItemDataProvider +import org.smartregister.fhircore.engine.ui.questionnaire.items.PickerPatient +import timber.log.Timber + +class PatientPickerViewModel( + private val lifecycleCoroutineScope: CoroutineScope, + private val customQuestItemDataProvider: CustomQuestItemDataProvider, +) { + private val _state = MutableLiveData>>() + val state: LiveData>> + get() = _state + + fun submitText(input: String) { + _state.value = DataLoadState.Loading + lifecycleCoroutineScope.launch { + try { + val patients = customQuestItemDataProvider.searchPatients(input) + _state.value = DataLoadState.Success(patients) + } catch (e: Exception) { + Timber.e(e) + _state.value = DataLoadState.Error(e) + } + } + } +} diff --git a/android/engine/src/main/res/layout/custom_patient_picker_item.xml b/android/engine/src/main/res/layout/custom_patient_picker_item.xml new file mode 100644 index 0000000000..92723a79b7 --- /dev/null +++ b/android/engine/src/main/res/layout/custom_patient_picker_item.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/android/engine/src/main/res/layout/custom_quest_patient_picker_dialog.xml b/android/engine/src/main/res/layout/custom_quest_patient_picker_dialog.xml new file mode 100644 index 0000000000..cf72bb4289 --- /dev/null +++ b/android/engine/src/main/res/layout/custom_quest_patient_picker_dialog.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + +