From 197061439a9941e3b5cdb427efd9752c7eb82312 Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 27 Nov 2025 13:28:16 +0530 Subject: [PATCH 1/4] Add `Suspected` flag to recent patient list items --- .../simple/clinic/patient/RecentPatient.kt | 73 ++++++++++++------- .../clinic/recentpatient/RecentPatientItem.kt | 29 +++++++- .../recentpatientsview/RecentPatientItem.kt | 26 ++++++- .../res/layout/recent_patient_item_view.xml | 7 +- app/src/main/res/values/strings.xml | 3 + .../LatestRecentPatientsLogicTest.kt | 14 ++++ .../kotlin/org/simple/clinic/TestData.kt | 7 +- 7 files changed, 121 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/org/simple/clinic/patient/RecentPatient.kt b/app/src/main/java/org/simple/clinic/patient/RecentPatient.kt index ee0887f4fc4..ce4fb5d27ef 100644 --- a/app/src/main/java/org/simple/clinic/patient/RecentPatient.kt +++ b/app/src/main/java/org/simple/clinic/patient/RecentPatient.kt @@ -12,6 +12,7 @@ import org.simple.clinic.overdue.Appointment.Status import org.simple.clinic.util.Unicode import java.time.Instant import java.util.UUID +import org.simple.clinic.medicalhistory.Answer as MedicalHistoryAnswer @Parcelize data class RecentPatient( @@ -30,6 +31,10 @@ data class RecentPatient( val updatedAt: Instant, val eligibleForReassignment: Answer, + + val diagnosedWithHypertension: MedicalHistoryAnswer?, + + val diagnosedWithDiabetes: MedicalHistoryAnswer? ) : Parcelable { override fun toString(): String { @@ -43,43 +48,57 @@ data class RecentPatient( const val RECENT_PATIENT_QUERY = """ SELECT P.uuid, P.fullName, P.gender, P.dateOfBirth, P.age_value, - P.age_updatedAt, P.recordedAt patientRecordedAt, P.eligibleForReassignment, - MAX( - IFNULL(BP.latestRecordedAt, '0'), - IFNULL(PD.latestUpdatedAt, '0'), - IFNULL(AP.latestCreatedAt, '0'), - IFNULL(BloodSugar.latestRecordedAt, '0') - ) updatedAt + P.age_updatedAt, P.recordedAt patientRecordedAt, P.eligibleForReassignment, + ( + SELECT diagnosedWithHypertension + FROM MedicalHistory + WHERE patientUuid = P.uuid + ORDER BY updatedAt DESC + LIMIT 1 + ) diagnosedWithHypertension, + ( + SELECT hasDiabetes + FROM MedicalHistory + WHERE patientUuid = P.uuid + ORDER BY updatedAt DESC + LIMIT 1 + ) diagnosedWithDiabetes, + MAX( + IFNULL(BP.latestRecordedAt, '0'), + IFNULL(PD.latestUpdatedAt, '0'), + IFNULL(AP.latestCreatedAt, '0'), + IFNULL(BloodSugar.latestRecordedAt, '0') + ) updatedAt FROM Patient P LEFT JOIN ( SELECT MAX(recordedAt) latestRecordedAt, patientUuid, facilityUuid - FROM BloodPressureMeasurement - WHERE facilityUuid = :facilityUuid + FROM BloodPressureMeasurement + WHERE facilityUuid = :facilityUuid AND deletedAt IS NULL - GROUP BY patientUuid + GROUP BY patientUuid ) BP ON P.uuid = BP.patientUuid LEFT JOIN ( SELECT MAX(updatedAt) latestUpdatedAt, patientUuid, facilityUuid - FROM PrescribedDrug - WHERE facilityUuid = :facilityUuid + FROM PrescribedDrug + WHERE facilityUuid = :facilityUuid AND deletedAt IS NULL - GROUP BY patientUuid + GROUP BY patientUuid ) PD ON P.uuid = PD.patientUuid LEFT JOIN ( SELECT MAX(createdAt) latestCreatedAt, uuid, patientUuid, facilityUuid, creationFacilityUuid - FROM Appointment - WHERE creationFacilityUuid = :facilityUuid + FROM Appointment + WHERE creationFacilityUuid = :facilityUuid AND deletedAt IS NULL AND status = :appointmentStatus AND appointmentType = :appointmentType - GROUP BY patientUuid + GROUP BY patientUuid ) AP ON P.uuid = AP.patientUuid LEFT JOIN ( SELECT MAX(recordedAt) latestRecordedAt, patientUuid, facilityUuid - FROM BloodSugarMeasurements - WHERE facilityUuid = :facilityUuid + FROM BloodSugarMeasurements + WHERE facilityUuid = :facilityUuid AND deletedAt IS NULL - GROUP BY patientUuid + GROUP BY patientUuid ) BloodSugar ON P.uuid = BloodSugar.patientUuid WHERE ( ( @@ -87,7 +106,7 @@ data class RecentPatient( PD.facilityUuid = :facilityUuid OR AP.creationFacilityUuid = :facilityUuid OR BloodSugar.facilityUuid = :facilityUuid - ) + ) AND P.deletedAt IS NULL AND P.status = :patientStatus ) @@ -96,14 +115,12 @@ data class RecentPatient( } /** - Goal: Fetch a list of patients with 10 most recent changes. - There are tables like BloodPressureMeasurement (BP), PrescribedDrug (PD), Appointment (AP), etc. Let’s call each table T1, T2, T3, etc. - - Algo: - 1. Get a list of all patients - 2. For each patient, from each table T, get the latest change for them. Columns: T1.latestUpdatedAt, T2.latestUpdatedAt, etc. - 3. Pick latestUpdatedAt for each patient - 4. Order by updatedAt from final list and cap it to 10 entries. + * Goal: Fetch a list of patients with 10 most recent changes. + * Algo: + * 1. Get a list of all patients + * 2. For each patient, from each table T, get the latest change for them. + * 3. Pick latestUpdatedAt for each patient + * 4. Order by updatedAt from final list and cap it to the limit. */ @Query("$RECENT_PATIENT_QUERY LIMIT :limit") fun recentPatientsWithLimit( diff --git a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt index 1b99f434561..57e646ca9a6 100644 --- a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.DiffUtil import io.reactivex.subjects.Subject import org.simple.clinic.R import org.simple.clinic.databinding.RecentPatientItemViewBinding +import org.simple.clinic.medicalhistory.Answer.Suspected import org.simple.clinic.patient.Gender import org.simple.clinic.patient.RecentPatient import org.simple.clinic.patient.displayIconRes @@ -28,7 +29,9 @@ data class RecentPatientItem( val updatedAt: Instant, val dateFormatter: DateTimeFormatter, val clock: UserClock, - val isNewRegistration: Boolean + val isNewRegistration: Boolean, + val isSuspectedForHypertension: Boolean, + val isSuspectedForDiabetes: Boolean, ) : PagingItemAdapter.Item { companion object { @@ -59,7 +62,9 @@ data class RecentPatientItem( updatedAt = recentPatient.updatedAt, dateFormatter = dateFormatter, clock = userClock, - isNewRegistration = isNewRegistration + isNewRegistration = isNewRegistration, + isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected, + isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected ) } } @@ -74,7 +79,25 @@ data class RecentPatientItem( subject.onNext(RecentPatientItemClicked(patientUuid = uuid)) } - binding.newRegistrationTextView.visibleOrGone(isNewRegistration) + val statusText: String? = when { + isSuspectedForHypertension && isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes) + + isSuspectedForHypertension -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension) + + isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_diabetes) + + isNewRegistration -> + context.getString(R.string.recent_patients_itemview_new_registration) + + else -> null + } + + binding.patientStatusTextView.visibleOrGone(statusText != null) + binding.patientStatusTextView.text = statusText + binding.patientNameTextView.text = context.resources.getString(R.string.patients_recentpatients_nameage, name, age.toString()) binding.genderImageView.setImageResource(gender.displayIconRes) binding.lastSeenTextView.text = dateFormatter.format(updatedAt.toLocalDateAtZone(clock.zone)) diff --git a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt index 87159d57dc3..2b8c69dce43 100644 --- a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt @@ -5,6 +5,7 @@ import io.reactivex.subjects.Subject import org.simple.clinic.R import org.simple.clinic.databinding.RecentPatientItemViewBinding import org.simple.clinic.databinding.SeeAllItemViewBinding +import org.simple.clinic.medicalhistory.Answer.Suspected import org.simple.clinic.patient.Answer import org.simple.clinic.patient.Gender import org.simple.clinic.patient.RecentPatient @@ -63,7 +64,9 @@ sealed class RecentPatientItemType : ItemAdapter.Item { clock = userClock, isNewRegistration = isNewRegistration, isEligibleForReassignment = isPatientReassignmentFeatureEnabled && - recentPatient.eligibleForReassignment == Answer.Yes + recentPatient.eligibleForReassignment == Answer.Yes, + isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected, + isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected ) } } @@ -79,6 +82,8 @@ data class RecentPatientItem( val clock: UserClock, val isNewRegistration: Boolean, val isEligibleForReassignment: Boolean, + val isSuspectedForHypertension: Boolean, + val isSuspectedForDiabetes: Boolean, ) : RecentPatientItemType() { override fun layoutResId(): Int = R.layout.recent_patient_item_view @@ -91,7 +96,24 @@ data class RecentPatientItem( subject.onNext(RecentPatientItemClicked(patientUuid = uuid)) } - binding.newRegistrationTextView.visibleOrGone(isNewRegistration) + val statusText: String? = when { + isSuspectedForHypertension && isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes) + + isSuspectedForHypertension -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension) + + isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_diabetes) + + isNewRegistration -> + context.getString(R.string.recent_patients_itemview_new_registration) + + else -> null + } + + binding.patientStatusTextView.visibleOrGone(statusText != null) + binding.patientStatusTextView.text = statusText binding.facilityReassignmentView.root.visibleOrGone(isEligibleForReassignment) binding.patientNameTextView.text = context.resources.getString(R.string.patients_recentpatients_nameage, name, age.toString()) binding.genderImageView.setImageResource(gender.displayIconRes) diff --git a/app/src/main/res/layout/recent_patient_item_view.xml b/app/src/main/res/layout/recent_patient_item_view.xml index 2cfa663b777..21688dc8bd3 100644 --- a/app/src/main/res/layout/recent_patient_item_view.xml +++ b/app/src/main/res/layout/recent_patient_item_view.xml @@ -31,18 +31,17 @@ android:layout_marginBottom="@dimen/spacing_4" android:textAppearance="?attr/textAppearanceSubtitle1" android:textColor="?attr/colorPrimary" - app:layout_constraintBottom_toTopOf="@id/newRegistrationTextView" + app:layout_constraintBottom_toTopOf="@id/patientStatusTextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/genderImageView" app:layout_constraintTop_toTopOf="@+id/genderImageView" tools:text="Anish Acharya, 43" /> + app:layout_constraintTop_toBottomOf="@id/patientStatusTextView" /> Visited:\u0020 New registration + Suspected for hypertension, diabetes + Suspected for hypertension + Suspected for diabetes diff --git a/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt b/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt index d130c61a36b..79bdb0c9ec2 100644 --- a/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt +++ b/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt @@ -114,6 +114,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = true, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ), RecentPatientItem( uuid = patientUuid2, @@ -125,6 +127,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = false, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ), RecentPatientItem( uuid = patientUuid3, @@ -136,6 +140,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = false, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ) )) verify(ui).showOrHideRecentPatients(isVisible = true) @@ -210,6 +216,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = true, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ), RecentPatientItem( uuid = patientUuid2, @@ -221,6 +229,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = false, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ), RecentPatientItem( uuid = patientUuid3, @@ -232,6 +242,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = false, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ), SeeAllItem )) @@ -275,6 +287,8 @@ class LatestRecentPatientsLogicTest { clock = userClock, isNewRegistration = false, isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, ) )) verify(ui).showOrHideRecentPatients(true) diff --git a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt index 7ac145066ee..b1f8c38044c 100644 --- a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt +++ b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt @@ -1167,7 +1167,10 @@ object TestData { dateOfBirth = null ), eligibleForReassignment: PatientAnswer = PatientAnswer.Unanswered, - ) = RecentPatient( + diagnosedWithHypertension: Answer = Answer.Unanswered, + diagnosedWithDiabetes: Answer = Answer.Unanswered, + + ) = RecentPatient( uuid = uuid, fullName = fullName, gender = gender, @@ -1175,6 +1178,8 @@ object TestData { patientRecordedAt = patientRecordedAt, updatedAt = updatedAt, eligibleForReassignment = eligibleForReassignment, + diagnosedWithHypertension = diagnosedWithHypertension, + diagnosedWithDiabetes = diagnosedWithDiabetes, ) fun ongoingLoginEntry( From ee0686d494405ceff5459f14a6d28958a43a1d84 Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 27 Nov 2025 15:12:13 +0530 Subject: [PATCH 2/4] Refactor `RecentPatientItem` --- .../clinic/recentpatient/RecentPatientItem.kt | 72 +++---------- .../recentpatient/RecentPatientUiModel.kt | 102 ++++++++++++++++++ .../recentpatientsview/RecentPatientItem.kt | 73 +++---------- 3 files changed, 128 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt diff --git a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt index 57e646ca9a6..657560ea71b 100644 --- a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt @@ -4,34 +4,17 @@ import androidx.paging.PagingData import androidx.paging.map import androidx.recyclerview.widget.DiffUtil import io.reactivex.subjects.Subject -import org.simple.clinic.R -import org.simple.clinic.databinding.RecentPatientItemViewBinding -import org.simple.clinic.medicalhistory.Answer.Suspected -import org.simple.clinic.patient.Gender import org.simple.clinic.patient.RecentPatient -import org.simple.clinic.patient.displayIconRes import org.simple.clinic.util.UserClock -import org.simple.clinic.util.toLocalDateAtZone import org.simple.clinic.widgets.PagingItemAdapter import org.simple.clinic.widgets.UiEvent import org.simple.clinic.widgets.recyclerview.BindingViewHolder -import org.simple.clinic.widgets.visibleOrGone -import java.time.Instant import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.UUID data class RecentPatientItem( - val uuid: UUID, - val name: String, - val age: Int, - val gender: Gender, - val updatedAt: Instant, - val dateFormatter: DateTimeFormatter, - val clock: UserClock, - val isNewRegistration: Boolean, - val isSuspectedForHypertension: Boolean, - val isSuspectedForDiabetes: Boolean, + private val model: RecentPatientUiModel ) : PagingItemAdapter.Item { companion object { @@ -41,7 +24,6 @@ data class RecentPatientItem( dateFormatter: DateTimeFormatter ): PagingData { val today = LocalDate.now(userClock) - return recentPatients.map { recentPatientItem(it, today, userClock, dateFormatter) } } @@ -51,56 +33,26 @@ data class RecentPatientItem( userClock: UserClock, dateFormatter: DateTimeFormatter ): RecentPatientItem { - val patientRegisteredOnDate = recentPatient.patientRecordedAt.toLocalDateAtZone(userClock.zone) - val isNewRegistration = today == patientRegisteredOnDate - - return RecentPatientItem( - uuid = recentPatient.uuid, - name = recentPatient.fullName, - age = recentPatient.ageDetails.estimateAge(userClock), - gender = recentPatient.gender, - updatedAt = recentPatient.updatedAt, + val model = RecentPatientUiModel.fromDomain( + recentPatient = recentPatient, + today = today, + userClock = userClock, dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = isNewRegistration, - isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected, - isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected + isEligibleForReassignment = false ) + + return RecentPatientItem(model) } } - override fun layoutResId(): Int = R.layout.recent_patient_item_view + val uuid: UUID get() = model.uuid - override fun render(holder: BindingViewHolder, subject: Subject) { - val context = holder.itemView.context - val binding = holder.binding as RecentPatientItemViewBinding + override fun layoutResId(): Int = org.simple.clinic.R.layout.recent_patient_item_view - holder.itemView.setOnClickListener { + override fun render(holder: BindingViewHolder, subject: Subject) { + RecentPatientViewBinder.bind(holder, model) { subject.onNext(RecentPatientItemClicked(patientUuid = uuid)) } - - val statusText: String? = when { - isSuspectedForHypertension && isSuspectedForDiabetes -> - context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes) - - isSuspectedForHypertension -> - context.getString(R.string.recent_patients_itemview_suspected_for_hypertension) - - isSuspectedForDiabetes -> - context.getString(R.string.recent_patients_itemview_suspected_for_diabetes) - - isNewRegistration -> - context.getString(R.string.recent_patients_itemview_new_registration) - - else -> null - } - - binding.patientStatusTextView.visibleOrGone(statusText != null) - binding.patientStatusTextView.text = statusText - - binding.patientNameTextView.text = context.resources.getString(R.string.patients_recentpatients_nameage, name, age.toString()) - binding.genderImageView.setImageResource(gender.displayIconRes) - binding.lastSeenTextView.text = dateFormatter.format(updatedAt.toLocalDateAtZone(clock.zone)) } } diff --git a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt new file mode 100644 index 00000000000..93988bf8e03 --- /dev/null +++ b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt @@ -0,0 +1,102 @@ +package org.simple.clinic.recentpatient + +import org.simple.clinic.R +import org.simple.clinic.databinding.RecentPatientItemViewBinding +import org.simple.clinic.medicalhistory.Answer.Suspected +import org.simple.clinic.patient.Gender +import org.simple.clinic.patient.RecentPatient +import org.simple.clinic.patient.displayIconRes +import org.simple.clinic.util.UserClock +import org.simple.clinic.util.toLocalDateAtZone +import org.simple.clinic.widgets.recyclerview.BindingViewHolder +import org.simple.clinic.widgets.visibleOrGone +import java.time.Instant +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.UUID + +/** + * Shared UI model used by both paged and non-paged adapters. + */ +data class RecentPatientUiModel( + val uuid: UUID, + val name: String, + val age: Int, + val gender: Gender, + val updatedAt: Instant, + val dateFormatter: DateTimeFormatter, + val clock: UserClock, + val isNewRegistration: Boolean, + val isEligibleForReassignment: Boolean = false, + val isSuspectedForHypertension: Boolean = false, + val isSuspectedForDiabetes: Boolean = false +) { + companion object { + fun fromDomain( + recentPatient: RecentPatient, + today: LocalDate, + userClock: UserClock, + dateFormatter: DateTimeFormatter, + isEligibleForReassignment: Boolean = false + ): RecentPatientUiModel { + val patientRegisteredOnDate = recentPatient.patientRecordedAt.toLocalDateAtZone(userClock.zone) + val isNewRegistration = today == patientRegisteredOnDate + + return RecentPatientUiModel( + uuid = recentPatient.uuid, + name = recentPatient.fullName, + age = recentPatient.ageDetails.estimateAge(userClock), + gender = recentPatient.gender, + updatedAt = recentPatient.updatedAt, + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = isNewRegistration, + isEligibleForReassignment = isEligibleForReassignment, + isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected, + isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected + ) + } + } +} + +object RecentPatientViewBinder { + + fun bind( + holder: BindingViewHolder, + model: RecentPatientUiModel, + onClick: (UUID) -> Unit + ) { + val context = holder.itemView.context + val binding = holder.binding as RecentPatientItemViewBinding + + holder.itemView.setOnClickListener { + onClick(model.uuid) + } + + val statusText: String? = when { + model.isSuspectedForHypertension && model.isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes) + + model.isSuspectedForHypertension -> + context.getString(R.string.recent_patients_itemview_suspected_for_hypertension) + + model.isSuspectedForDiabetes -> + context.getString(R.string.recent_patients_itemview_suspected_for_diabetes) + + model.isNewRegistration -> + context.getString(R.string.recent_patients_itemview_new_registration) + + else -> null + } + + binding.patientStatusTextView.visibleOrGone(statusText != null) + binding.patientStatusTextView.text = statusText + binding.facilityReassignmentView.root.visibleOrGone(model.isEligibleForReassignment) + + binding.patientNameTextView.text = + context.resources.getString(R.string.patients_recentpatients_nameage, model.name, model.age.toString()) + + binding.genderImageView.setImageResource(model.gender.displayIconRes) + binding.lastSeenTextView.text = model.dateFormatter.format(model.updatedAt.toLocalDateAtZone(model.clock.zone)) + } +} diff --git a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt index 2b8c69dce43..e7df3b63445 100644 --- a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt @@ -3,24 +3,20 @@ package org.simple.clinic.recentpatientsview import androidx.recyclerview.widget.DiffUtil import io.reactivex.subjects.Subject import org.simple.clinic.R -import org.simple.clinic.databinding.RecentPatientItemViewBinding import org.simple.clinic.databinding.SeeAllItemViewBinding -import org.simple.clinic.medicalhistory.Answer.Suspected import org.simple.clinic.patient.Answer -import org.simple.clinic.patient.Gender import org.simple.clinic.patient.RecentPatient -import org.simple.clinic.patient.displayIconRes +import org.simple.clinic.recentpatient.RecentPatientUiModel +import org.simple.clinic.recentpatient.RecentPatientViewBinder import org.simple.clinic.util.UserClock -import org.simple.clinic.util.toLocalDateAtZone import org.simple.clinic.widgets.ItemAdapter import org.simple.clinic.widgets.UiEvent import org.simple.clinic.widgets.recyclerview.BindingViewHolder -import org.simple.clinic.widgets.visibleOrGone -import java.time.Instant import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.UUID + sealed class RecentPatientItemType : ItemAdapter.Item { companion object { @@ -51,73 +47,32 @@ sealed class RecentPatientItemType : ItemAdapter.Item { dateFormatter: DateTimeFormatter, isPatientReassignmentFeatureEnabled: Boolean, ): RecentPatientItem { - val patientRegisteredOnDate = recentPatient.patientRecordedAt.toLocalDateAtZone(userClock.zone) - val isNewRegistration = today == patientRegisteredOnDate - - return RecentPatientItem( - uuid = recentPatient.uuid, - name = recentPatient.fullName, - age = recentPatient.ageDetails.estimateAge(userClock), - gender = recentPatient.gender, - updatedAt = recentPatient.updatedAt, + val model = RecentPatientUiModel.fromDomain( + recentPatient = recentPatient, + today = today, + userClock = userClock, dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = isNewRegistration, isEligibleForReassignment = isPatientReassignmentFeatureEnabled && - recentPatient.eligibleForReassignment == Answer.Yes, - isSuspectedForHypertension = recentPatient.diagnosedWithHypertension == Suspected, - isSuspectedForDiabetes = recentPatient.diagnosedWithDiabetes == Suspected + recentPatient.eligibleForReassignment == Answer.Yes ) + + return RecentPatientItem(model) } } } data class RecentPatientItem( - val uuid: UUID, - val name: String, - val age: Int, - val gender: Gender, - val updatedAt: Instant, - val dateFormatter: DateTimeFormatter, - val clock: UserClock, - val isNewRegistration: Boolean, - val isEligibleForReassignment: Boolean, - val isSuspectedForHypertension: Boolean, - val isSuspectedForDiabetes: Boolean, + private val model: RecentPatientUiModel ) : RecentPatientItemType() { + val uuid: UUID get() = model.uuid + override fun layoutResId(): Int = R.layout.recent_patient_item_view override fun render(holder: BindingViewHolder, subject: Subject) { - val context = holder.itemView.context - val binding = holder.binding as RecentPatientItemViewBinding - - holder.itemView.setOnClickListener { + RecentPatientViewBinder.bind(holder, model) { subject.onNext(RecentPatientItemClicked(patientUuid = uuid)) } - - val statusText: String? = when { - isSuspectedForHypertension && isSuspectedForDiabetes -> - context.getString(R.string.recent_patients_itemview_suspected_for_hypertension_and_diabetes) - - isSuspectedForHypertension -> - context.getString(R.string.recent_patients_itemview_suspected_for_hypertension) - - isSuspectedForDiabetes -> - context.getString(R.string.recent_patients_itemview_suspected_for_diabetes) - - isNewRegistration -> - context.getString(R.string.recent_patients_itemview_new_registration) - - else -> null - } - - binding.patientStatusTextView.visibleOrGone(statusText != null) - binding.patientStatusTextView.text = statusText - binding.facilityReassignmentView.root.visibleOrGone(isEligibleForReassignment) - binding.patientNameTextView.text = context.resources.getString(R.string.patients_recentpatients_nameage, name, age.toString()) - binding.genderImageView.setImageResource(gender.displayIconRes) - binding.lastSeenTextView.text = dateFormatter.format(updatedAt.toLocalDateAtZone(clock.zone)) } } From 41ebbfe745341816254937edf00393f7c0220e21 Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 27 Nov 2025 15:18:40 +0530 Subject: [PATCH 3/4] Fix test cases --- .../patient/PatientRepositoryAndroidTest.kt | 6 + .../clinic/recentpatient/RecentPatientItem.kt | 2 +- .../recentpatient/RecentPatientUiModel.kt | 2 +- .../recentpatientsview/RecentPatientItem.kt | 2 +- .../LatestRecentPatientsLogicTest.kt | 187 ++++++++++-------- 5 files changed, 110 insertions(+), 89 deletions(-) diff --git a/app/src/androidTest/java/org/simple/clinic/patient/PatientRepositoryAndroidTest.kt b/app/src/androidTest/java/org/simple/clinic/patient/PatientRepositoryAndroidTest.kt index 9769f719e97..8e8e4cd8bb0 100644 --- a/app/src/androidTest/java/org/simple/clinic/patient/PatientRepositoryAndroidTest.kt +++ b/app/src/androidTest/java/org/simple/clinic/patient/PatientRepositoryAndroidTest.kt @@ -922,6 +922,8 @@ class PatientRepositoryAndroidTest { patientRecordedAt = this.recordedAt, updatedAt = recordedAt, eligibleForReassignment = eligibleForReassignment, + diagnosedWithDiabetes = Yes, + diagnosedWithHypertension = Yes ) private fun verifyRecentPatientOrder( @@ -977,6 +979,8 @@ class PatientRepositoryAndroidTest { patientRecordedAt = this.recordedAt, updatedAt = updatedAt, eligibleForReassignment = eligibleForReassignment, + diagnosedWithDiabetes = Yes, + diagnosedWithHypertension = Yes ) } } @@ -1119,6 +1123,8 @@ class PatientRepositoryAndroidTest { patientRecordedAt = this.recordedAt, updatedAt = createdAt, eligibleForReassignment = eligibleForReassignment, + diagnosedWithDiabetes = Yes, + diagnosedWithHypertension = Yes ) } } diff --git a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt index 657560ea71b..282db1309e6 100644 --- a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientItem.kt @@ -33,7 +33,7 @@ data class RecentPatientItem( userClock: UserClock, dateFormatter: DateTimeFormatter ): RecentPatientItem { - val model = RecentPatientUiModel.fromDomain( + val model = RecentPatientUiModel.from( recentPatient = recentPatient, today = today, userClock = userClock, diff --git a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt index 93988bf8e03..7ab6b5b77b5 100644 --- a/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt +++ b/app/src/main/java/org/simple/clinic/recentpatient/RecentPatientUiModel.kt @@ -32,7 +32,7 @@ data class RecentPatientUiModel( val isSuspectedForDiabetes: Boolean = false ) { companion object { - fun fromDomain( + fun from( recentPatient: RecentPatient, today: LocalDate, userClock: UserClock, diff --git a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt index e7df3b63445..31ade58efb8 100644 --- a/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt +++ b/app/src/main/java/org/simple/clinic/recentpatientsview/RecentPatientItem.kt @@ -47,7 +47,7 @@ sealed class RecentPatientItemType : ItemAdapter.Item { dateFormatter: DateTimeFormatter, isPatientReassignmentFeatureEnabled: Boolean, ): RecentPatientItem { - val model = RecentPatientUiModel.fromDomain( + val model = RecentPatientUiModel.from( recentPatient = recentPatient, today = today, userClock = userClock, diff --git a/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt b/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt index 79bdb0c9ec2..b047565e8cd 100644 --- a/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt +++ b/app/src/test/java/org/simple/clinic/recentpatientsview/LatestRecentPatientsLogicTest.kt @@ -1,11 +1,5 @@ package org.simple.clinic.recentpatientsview -import org.mockito.kotlin.clearInvocations -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions -import org.mockito.kotlin.whenever import io.reactivex.Observable import io.reactivex.rxkotlin.ofType import io.reactivex.subjects.PublishSubject @@ -13,6 +7,13 @@ import io.reactivex.subjects.Subject import org.junit.After import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import org.simple.clinic.TestData import org.simple.clinic.patient.Gender.Female import org.simple.clinic.patient.Gender.Male import org.simple.clinic.patient.Gender.Transgender @@ -20,12 +21,12 @@ import org.simple.clinic.patient.PatientAgeDetails import org.simple.clinic.patient.PatientConfig import org.simple.clinic.patient.PatientRepository import org.simple.clinic.patient.RecentPatient +import org.simple.clinic.recentpatient.RecentPatientUiModel +import org.simple.clinic.util.RxErrorsRule +import org.simple.clinic.util.TestUserClock import org.simple.clinic.util.scheduler.TestSchedulersProvider import org.simple.clinic.widgets.UiEvent import org.simple.mobius.migration.MobiusTestFixture -import org.simple.clinic.TestData -import org.simple.clinic.util.RxErrorsRule -import org.simple.clinic.util.TestUserClock import java.time.Instant import java.time.LocalDate import java.time.Period @@ -105,43 +106,49 @@ class LatestRecentPatientsLogicTest { verify(ui).updateRecentPatients(listOf( RecentPatientItem( - uuid = patientUuid1, - name = "Ajay Kumar", - age = 42, - gender = Transgender, - updatedAt = Instant.parse("2020-01-01T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = true, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid1, + name = "Ajay Kumar", + age = 42, + gender = Transgender, + updatedAt = Instant.parse("2020-01-01T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = true, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ), RecentPatientItem( - uuid = patientUuid2, - name = "Vijay Kumar", - age = 24, - gender = Male, - updatedAt = Instant.parse("2019-12-31T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = false, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid2, + name = "Vijay Kumar", + age = 24, + gender = Male, + updatedAt = Instant.parse("2019-12-31T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = false, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ), RecentPatientItem( - uuid = patientUuid3, - name = "Vinaya Kumari", - age = 27, - gender = Female, - updatedAt = Instant.parse("2019-12-29T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = false, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid3, + name = "Vinaya Kumari", + age = 27, + gender = Female, + updatedAt = Instant.parse("2019-12-29T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = false, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ) )) verify(ui).showOrHideRecentPatients(isVisible = true) @@ -207,43 +214,49 @@ class LatestRecentPatientsLogicTest { verify(ui).updateRecentPatients(listOf( RecentPatientItem( - uuid = patientUuid1, - name = "Ajay Kumar", - age = 42, - gender = Transgender, - updatedAt = Instant.parse("2020-01-01T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = true, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid1, + name = "Ajay Kumar", + age = 42, + gender = Transgender, + updatedAt = Instant.parse("2020-01-01T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = true, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ), RecentPatientItem( - uuid = patientUuid2, - name = "Vijay Kumar", - age = 24, - gender = Male, - updatedAt = Instant.parse("2019-12-31T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = false, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid2, + name = "Vijay Kumar", + age = 24, + gender = Male, + updatedAt = Instant.parse("2019-12-31T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = false, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ), RecentPatientItem( - uuid = patientUuid3, - name = "Vinaya Kumari", - age = 27, - gender = Female, - updatedAt = Instant.parse("2019-12-28T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = false, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid3, + name = "Vinaya Kumari", + age = 27, + gender = Female, + updatedAt = Instant.parse("2019-12-28T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = false, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ), SeeAllItem )) @@ -278,17 +291,19 @@ class LatestRecentPatientsLogicTest { ))) verify(ui).updateRecentPatients(listOf( RecentPatientItem( - uuid = patientUuid, - name = "Anish Acharya", - age = 2, - gender = Male, - updatedAt = Instant.parse("2018-01-01T00:00:00Z"), - dateFormatter = dateFormatter, - clock = userClock, - isNewRegistration = false, - isEligibleForReassignment = false, - isSuspectedForHypertension = false, - isSuspectedForDiabetes = false, + model = RecentPatientUiModel( + uuid = patientUuid, + name = "Anish Acharya", + age = 2, + gender = Male, + updatedAt = Instant.parse("2018-01-01T00:00:00Z"), + dateFormatter = dateFormatter, + clock = userClock, + isNewRegistration = false, + isEligibleForReassignment = false, + isSuspectedForHypertension = false, + isSuspectedForDiabetes = false, + ) ) )) verify(ui).showOrHideRecentPatients(true) From dbae79711db2f5b99fed66d4e3f295000476222a Mon Sep 17 00:00:00 2001 From: sagarwal Date: Thu, 27 Nov 2025 15:23:55 +0530 Subject: [PATCH 4/4] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d9b1db1ad..ec78eb4341e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Handle Screening feature visibility based on feature flag `Screening` - Add `hypertensionDiagnosedAt` and `diabetesDiagnosedAt` in `MedicalHistory` table - Hide schedule appointment sheet for suspected patients +- Add `Suspected` flag to recent patient list item ## 2025.09.09