From cf9d04009c2b21806a657b9bb82aef75ff4da2af Mon Sep 17 00:00:00 2001 From: Jose Francisco Date: Thu, 5 Sep 2024 17:03:22 +0100 Subject: [PATCH] (feat) Dynamically display patient identifiers in patient file. --- .../PatientDetailsRecyclerViewAdapter.kt | 413 ++++++++++-------- .../fhir/viewmodel/PatientListViewModel.kt | 4 + .../res/layout/patient_details_header.xml | 109 +++-- core/src/main/res/values/styles.xml | 2 +- 4 files changed, 280 insertions(+), 248 deletions(-) diff --git a/core/src/main/java/org/openmrs/android/fhir/adapters/PatientDetailsRecyclerViewAdapter.kt b/core/src/main/java/org/openmrs/android/fhir/adapters/PatientDetailsRecyclerViewAdapter.kt index 1c9e077..dfbc82a 100644 --- a/core/src/main/java/org/openmrs/android/fhir/adapters/PatientDetailsRecyclerViewAdapter.kt +++ b/core/src/main/java/org/openmrs/android/fhir/adapters/PatientDetailsRecyclerViewAdapter.kt @@ -20,13 +20,17 @@ import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView import androidx.annotation.ColorInt +import androidx.core.content.res.ResourcesCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.RoundedCornerTreatment import com.google.android.material.shape.ShapeAppearanceModel +import org.openmrs.android.fhir.R import org.openmrs.android.fhir.databinding.PatientDetailsCardViewBinding import org.openmrs.android.fhir.databinding.PatientDetailsHeaderBinding import org.openmrs.android.fhir.databinding.PatientDetailsUnsyncedBinding @@ -43,243 +47,272 @@ import org.openmrs.android.fhir.viewmodel.PatientDetailEncounter import org.openmrs.android.fhir.viewmodel.PatientDetailVisit class PatientDetailsRecyclerViewAdapter( - private val onCreateEncountersClick: () -> Unit, - private val onEditEncounterClick: (String, String, String) -> Unit, - private val onEditVisitClick: (String) -> Unit, + private val onCreateEncountersClick: () -> Unit, + private val onEditEncounterClick: (String, String, String) -> Unit, + private val onEditVisitClick: (String) -> Unit, ) : ListAdapter(PatientDetailsVisitItemViewHolder.PatientDetailDiffUtil()) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PatientDetailItemViewHolder { - return when (PatientDetailsVisitItemViewHolder.ViewTypes.from(viewType)) { - PatientDetailsVisitItemViewHolder.ViewTypes.HEADER -> - PatientDetailsHeaderItemViewHolder( - PatientDetailsCardViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), - ) - PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT -> - PatientOverviewItemViewHolder( - PatientDetailsHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onCreateEncountersClick - ) - PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_UNSYNCED -> - PatientDetailsUnsyncedItemViewHolder( - PatientDetailsUnsyncedBinding.inflate(LayoutInflater.from(parent.context),parent, false) - ) - PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_PROPERTY -> - PatientPropertyItemViewHolder( - PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), - ) - PatientDetailsVisitItemViewHolder.ViewTypes.OBSERVATION -> - PatientDetailsObservationItemViewHolder( - PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), - ) - PatientDetailsVisitItemViewHolder.ViewTypes.CONDITION -> - PatientDetailsVisitItemViewHolder.PatientDetailsConditionItemViewHolder( - PatientPropertyItemViewBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ), - ) - PatientDetailsVisitItemViewHolder.ViewTypes.ENCOUNTER -> - PatientDetailsEncounterItemViewHolder( - PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onEditEncounterClick - ) - PatientDetailsVisitItemViewHolder.ViewTypes.VISIT -> - PatientDetailsVisitItemViewHolder( - VisitListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onEditVisitClick - ) - } - } - - override fun onBindViewHolder(holder: PatientDetailItemViewHolder, position: Int) { - val model = getItem(position) - holder.bind(model) - if (holder is PatientDetailsHeaderItemViewHolder) return - if (holder is PatientDetailsEncounterItemViewHolder) { - holder.bind(getItem(position) as PatientDetailEncounter) - } - } - - override fun getItemViewType(position: Int): Int { - val item = getItem(position) - return when (item) { - is PatientDetailHeader -> PatientDetailsVisitItemViewHolder.ViewTypes.HEADER - is PatientDetailOverview -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT - is PatientDetailProperty -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_PROPERTY - is PatientDetailObservation -> PatientDetailsVisitItemViewHolder.ViewTypes.OBSERVATION - is PatientDetailCondition -> PatientDetailsVisitItemViewHolder.ViewTypes.CONDITION - is PatientUnsynced -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_UNSYNCED - is PatientDetailEncounter -> PatientDetailsVisitItemViewHolder.ViewTypes.ENCOUNTER - is PatientDetailVisit -> PatientDetailsVisitItemViewHolder.ViewTypes.VISIT - else -> { - throw IllegalArgumentException("Undefined Item type") - } - }.ordinal - } - - companion object { - private const val STROKE_WIDTH = 2f - private const val CORNER_RADIUS = 10f - - @ColorInt private const val FILL_COLOR = Color.TRANSPARENT - - @ColorInt private const val STROKE_COLOR = Color.GRAY - - fun allCornersRounded(): MaterialShapeDrawable { - return MaterialShapeDrawable( - ShapeAppearanceModel.builder() - .setAllCornerSizes(CORNER_RADIUS) - .setAllCorners(RoundedCornerTreatment()) - .build(), - ).applyStrokeColor() - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PatientDetailItemViewHolder { + return when (PatientDetailsVisitItemViewHolder.ViewTypes.from(viewType)) { + PatientDetailsVisitItemViewHolder.ViewTypes.HEADER -> + PatientDetailsHeaderItemViewHolder( + PatientDetailsCardViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT -> + PatientOverviewItemViewHolder( + PatientDetailsHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onCreateEncountersClick + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_UNSYNCED -> + PatientDetailsUnsyncedItemViewHolder( + PatientDetailsUnsyncedBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_PROPERTY -> + PatientPropertyItemViewHolder( + PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), + ) - fun topCornersRounded(): MaterialShapeDrawable { - return MaterialShapeDrawable( - ShapeAppearanceModel.builder() - .setTopLeftCornerSize(CORNER_RADIUS) - .setTopRightCornerSize(CORNER_RADIUS) - .setTopLeftCorner(RoundedCornerTreatment()) - .setTopRightCorner(RoundedCornerTreatment()) - .build(), - ).applyStrokeColor() + PatientDetailsVisitItemViewHolder.ViewTypes.OBSERVATION -> + PatientDetailsObservationItemViewHolder( + PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.CONDITION -> + PatientDetailsVisitItemViewHolder.PatientDetailsConditionItemViewHolder( + PatientPropertyItemViewBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.ENCOUNTER -> + PatientDetailsEncounterItemViewHolder( + PatientPropertyItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onEditEncounterClick + ) + + PatientDetailsVisitItemViewHolder.ViewTypes.VISIT -> + PatientDetailsVisitItemViewHolder( + VisitListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onEditVisitClick + ) + } } - fun bottomCornersRounded(): MaterialShapeDrawable { - return MaterialShapeDrawable( - ShapeAppearanceModel.builder() - .setBottomLeftCornerSize(CORNER_RADIUS) - .setBottomRightCornerSize(CORNER_RADIUS) - .setBottomLeftCorner(RoundedCornerTreatment()) - .setBottomRightCorner(RoundedCornerTreatment()) - .build(), - ).applyStrokeColor() + override fun onBindViewHolder(holder: PatientDetailItemViewHolder, position: Int) { + val model = getItem(position) + holder.bind(model) + if (holder is PatientDetailsHeaderItemViewHolder) return + if (holder is PatientDetailsEncounterItemViewHolder) { + holder.bind(getItem(position) as PatientDetailEncounter) + } } - fun noCornersRounded(): MaterialShapeDrawable { - return MaterialShapeDrawable(ShapeAppearanceModel.builder().build()).applyStrokeColor() + override fun getItemViewType(position: Int): Int { + val item = getItem(position) + return when (item) { + is PatientDetailHeader -> PatientDetailsVisitItemViewHolder.ViewTypes.HEADER + is PatientDetailOverview -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT + is PatientDetailProperty -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_PROPERTY + is PatientDetailObservation -> PatientDetailsVisitItemViewHolder.ViewTypes.OBSERVATION + is PatientDetailCondition -> PatientDetailsVisitItemViewHolder.ViewTypes.CONDITION + is PatientUnsynced -> PatientDetailsVisitItemViewHolder.ViewTypes.PATIENT_UNSYNCED + is PatientDetailEncounter -> PatientDetailsVisitItemViewHolder.ViewTypes.ENCOUNTER + is PatientDetailVisit -> PatientDetailsVisitItemViewHolder.ViewTypes.VISIT + else -> { + throw IllegalArgumentException("Undefined Item type") + } + }.ordinal } - private fun MaterialShapeDrawable.applyStrokeColor(): MaterialShapeDrawable { - strokeWidth = STROKE_WIDTH - fillColor = ColorStateList.valueOf(FILL_COLOR) - strokeColor = ColorStateList.valueOf(STROKE_COLOR) - return this + companion object { + private const val STROKE_WIDTH = 2f + private const val CORNER_RADIUS = 10f + + @ColorInt + private const val FILL_COLOR = Color.TRANSPARENT + + @ColorInt + private const val STROKE_COLOR = Color.GRAY + + fun allCornersRounded(): MaterialShapeDrawable { + return MaterialShapeDrawable( + ShapeAppearanceModel.builder() + .setAllCornerSizes(CORNER_RADIUS) + .setAllCorners(RoundedCornerTreatment()) + .build(), + ).applyStrokeColor() + } + + fun topCornersRounded(): MaterialShapeDrawable { + return MaterialShapeDrawable( + ShapeAppearanceModel.builder() + .setTopLeftCornerSize(CORNER_RADIUS) + .setTopRightCornerSize(CORNER_RADIUS) + .setTopLeftCorner(RoundedCornerTreatment()) + .setTopRightCorner(RoundedCornerTreatment()) + .build(), + ).applyStrokeColor() + } + + fun bottomCornersRounded(): MaterialShapeDrawable { + return MaterialShapeDrawable( + ShapeAppearanceModel.builder() + .setBottomLeftCornerSize(CORNER_RADIUS) + .setBottomRightCornerSize(CORNER_RADIUS) + .setBottomLeftCorner(RoundedCornerTreatment()) + .setBottomRightCorner(RoundedCornerTreatment()) + .build(), + ).applyStrokeColor() + } + + fun noCornersRounded(): MaterialShapeDrawable { + return MaterialShapeDrawable(ShapeAppearanceModel.builder().build()).applyStrokeColor() + } + + private fun MaterialShapeDrawable.applyStrokeColor(): MaterialShapeDrawable { + strokeWidth = STROKE_WIDTH + fillColor = ColorStateList.valueOf(FILL_COLOR) + strokeColor = ColorStateList.valueOf(STROKE_COLOR) + return this + } } - } } abstract class PatientDetailItemViewHolder(v: View) : RecyclerView.ViewHolder(v) { - abstract fun bind(data: PatientDetailData) + abstract fun bind(data: PatientDetailData) } class PatientOverviewItemViewHolder( - private val binding: PatientDetailsHeaderBinding, - val onCreateEncountersClick: () -> Unit, + private val binding: PatientDetailsHeaderBinding, + val onCreateEncountersClick: () -> Unit, ) : PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailOverview).let { - binding.title.text = it.patient.name + override fun bind(data: PatientDetailData) { + (data as PatientDetailOverview).let { + binding.title.text = it.patient.name + binding.identifiersContainer.removeAllViews() + it.patient.identifiers.forEach { identifier -> + if (!identifier.type?.text.equals("unsynced")) { + val textView = TextView(binding.root.context).apply { + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + textSize = 16f + typeface = ResourcesCompat.getFont(context, R.font.inter) + text = "${identifier.type.text}: ${identifier.value}" + } + binding.identifiersContainer.addView(textView) + } + } + } } - } } class PatientPropertyItemViewHolder(private val binding: PatientPropertyItemViewBinding) : - PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailProperty).let { - binding.name.text = it.patientProperty.header - binding.fieldName.text = it.patientProperty.value + PatientDetailItemViewHolder(binding.root) { + override fun bind(data: PatientDetailData) { + (data as PatientDetailProperty).let { + binding.name.text = it.patientProperty.header + binding.fieldName.text = it.patientProperty.value + } } - } } class PatientDetailsHeaderItemViewHolder(private val binding: PatientDetailsCardViewBinding) : - PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailHeader).let { binding.header.text = it.header } - } + PatientDetailItemViewHolder(binding.root) { + override fun bind(data: PatientDetailData) { + (data as PatientDetailHeader).let { binding.header.text = it.header } + } } class PatientDetailsUnsyncedItemViewHolder(private val binding: PatientDetailsUnsyncedBinding) : - PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - } + PatientDetailItemViewHolder(binding.root) { + override fun bind(data: PatientDetailData) { + } } + class PatientDetailsObservationItemViewHolder(private val binding: PatientPropertyItemViewBinding) : - PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailObservation).let { - binding.name.text = it.observation.code - binding.fieldName.text = it.observation.value + PatientDetailItemViewHolder(binding.root) { + override fun bind(data: PatientDetailData) { + (data as PatientDetailObservation).let { + binding.name.text = it.observation.code + binding.fieldName.text = it.observation.value + } } - } } class PatientDetailsEncounterItemViewHolder( - private val binding: PatientPropertyItemViewBinding, - private val onEditEncounterClick: (String, String, String) -> Unit + private val binding: PatientPropertyItemViewBinding, + private val onEditEncounterClick: (String, String, String) -> Unit ) : PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailEncounter).let { - val encounter = it.encounter; - binding.name.text = encounter.type - binding.fieldName.text = encounter.dateTime - binding.name.setOnClickListener { - onEditEncounterClick( encounter.encounterId ?: "", encounter.formDisplay ?: "", encounter.formResource ?: "") - } + override fun bind(data: PatientDetailData) { + (data as PatientDetailEncounter).let { + val encounter = it.encounter; + binding.name.text = encounter.type + binding.fieldName.text = encounter.dateTime + binding.name.setOnClickListener { + onEditEncounterClick( + encounter.encounterId ?: "", + encounter.formDisplay ?: "", + encounter.formResource ?: "" + ) + } + } } - } } class PatientDetailsVisitItemViewHolder( - private val binding: VisitListItemBinding, // Update to the correct binding class - private val onEditVisitClick: (String) -> Unit + private val binding: VisitListItemBinding, // Update to the correct binding class + private val onEditVisitClick: (String) -> Unit ) : PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailVisit).let { - val visit = it.visit - binding.encounterType.text = visit.code - binding.encounterDate.text = visit.getPeriods() - binding.encounterDate.setOnClickListener { - onEditVisitClick( visit.id ) - } + override fun bind(data: PatientDetailData) { + (data as PatientDetailVisit).let { + val visit = it.visit + binding.encounterType.text = visit.code + binding.encounterDate.text = visit.getPeriods() + binding.encounterDate.setOnClickListener { + onEditVisitClick(visit.id) + } + } } - } -class PatientDetailsConditionItemViewHolder(private val binding: PatientPropertyItemViewBinding) : - PatientDetailItemViewHolder(binding.root) { - override fun bind(data: PatientDetailData) { - (data as PatientDetailCondition).let { - binding.name.text = it.condition.code - binding.fieldName.text = it.condition.value + class PatientDetailsConditionItemViewHolder(private val binding: PatientPropertyItemViewBinding) : + PatientDetailItemViewHolder(binding.root) { + override fun bind(data: PatientDetailData) { + (data as PatientDetailCondition).let { + binding.name.text = it.condition.code + binding.fieldName.text = it.condition.value + } + } } - } -} -enum class ViewTypes { - HEADER, - PATIENT, - PATIENT_UNSYNCED, - PATIENT_PROPERTY, - OBSERVATION, - CONDITION, - ENCOUNTER, - VISIT; - - companion object { - fun from(ordinal: Int): ViewTypes { - return values()[ordinal] + enum class ViewTypes { + HEADER, + PATIENT, + PATIENT_UNSYNCED, + PATIENT_PROPERTY, + OBSERVATION, + CONDITION, + ENCOUNTER, + VISIT; + + companion object { + fun from(ordinal: Int): ViewTypes { + return values()[ordinal] + } + } } - } -} -class PatientDetailDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame(o: PatientDetailData, n: PatientDetailData) = o == n + class PatientDetailDiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame(o: PatientDetailData, n: PatientDetailData) = o == n - override fun areContentsTheSame(o: PatientDetailData, n: PatientDetailData) = areItemsTheSame(o, n) -} + override fun areContentsTheSame(o: PatientDetailData, n: PatientDetailData) = areItemsTheSame(o, n) + } } diff --git a/core/src/main/java/org/openmrs/android/fhir/viewmodel/PatientListViewModel.kt b/core/src/main/java/org/openmrs/android/fhir/viewmodel/PatientListViewModel.kt index 56696e2..a0b8334 100644 --- a/core/src/main/java/org/openmrs/android/fhir/viewmodel/PatientListViewModel.kt +++ b/core/src/main/java/org/openmrs/android/fhir/viewmodel/PatientListViewModel.kt @@ -28,6 +28,7 @@ import com.google.android.fhir.search.count import com.google.android.fhir.search.search import java.time.LocalDate import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.Identifier import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.ResourceType import org.hl7.fhir.r4.model.RiskAssessment @@ -143,6 +144,7 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi val city: String, val country: String, val isActive: Boolean, + val identifiers: List, val html: String, var isSynced: Boolean? = false, var risk: String? = "", @@ -224,6 +226,7 @@ internal fun Patient.toPatientItem(position: Int): PatientListViewModel.PatientI val phone = if (hasTelecom()) telecom[0].value else "" val city = if (hasAddress()) address[0].city else "" val country = if (hasAddress()) address[0].country else "" + val identifiers = identifier val isActive = active val html: String = if (hasText()) text.div.valueAsString else "" @@ -237,6 +240,7 @@ internal fun Patient.toPatientItem(position: Int): PatientListViewModel.PatientI city = city ?: "", country = country ?: "", isActive = isActive, + identifiers = identifiers, html = html, ) } diff --git a/core/src/main/res/layout/patient_details_header.xml b/core/src/main/res/layout/patient_details_header.xml index 7b70de2..6810d19 100644 --- a/core/src/main/res/layout/patient_details_header.xml +++ b/core/src/main/res/layout/patient_details_header.xml @@ -1,60 +1,55 @@ - + - - - - - - - - - - - - + xmlns:tools="http://schemas.android.com/tools" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="1dp" + android:layout_marginBottom="12sp"> + + + + + + + + + + + + + + + diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index de93302..8a32d97 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -16,7 +16,7 @@