Skip to content

Commit

Permalink
Migrate statin nudge to jetpack compose (#5090)
Browse files Browse the repository at this point in the history
Co-authored-by: Siddharth Agarwal <sagarwal@rtsl.org>
  • Loading branch information
siddh1004 and Siddharth Agarwal authored Oct 29, 2024
1 parent 820278b commit f0e820f
Show file tree
Hide file tree
Showing 19 changed files with 200 additions and 214 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Update translations: `bn-BD`
- Migrate `SplashScreen` to Compose
- Migrate `OnboardingScreen` to Compose
- Migrate `StainNudge` to Compose

### Features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ data class PatientSummaryModel(
val hasPrescribedDrugsChangedToday: Boolean?,
val scheduledAppointment: ParcelableOptional<Appointment>?,
val hasShownDiagnosisWarningDialog: Boolean,
val statin: StatinModel?,
val canPrescribeStatin: Boolean?,
) : Parcelable, PatientSummaryChildModel {

companion object {
Expand All @@ -46,7 +46,7 @@ data class PatientSummaryModel(
hasPrescribedDrugsChangedToday = null,
scheduledAppointment = null,
hasShownDiagnosisWarningDialog = false,
statin = null,
canPrescribeStatin = null,
)
}
}
Expand Down Expand Up @@ -82,7 +82,7 @@ data class PatientSummaryModel(
get() = scheduledAppointment != null && scheduledAppointment.isPresent()

val hasStatinInfoLoaded: Boolean
get() = statin != null
get() = canPrescribeStatin != null

override fun readyToRender(): Boolean {
return hasLoadedPatientSummaryProfile && hasLoadedCurrentFacility && hasPatientRegistrationData != null
Expand Down Expand Up @@ -128,7 +128,7 @@ data class PatientSummaryModel(
return copy(scheduledAppointment = appointment.toOptional().parcelable())
}

fun updateStatinInfo(statin: StatinModel): PatientSummaryModel {
return copy(statin = statin)
fun updateStatinInfo(canPrescribeStatin: Boolean): PatientSummaryModel {
return copy(canPrescribeStatin = canPrescribeStatin)
}
}
63 changes: 28 additions & 35 deletions app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.dynamicanimation.animation.DynamicAnimation
Expand All @@ -32,6 +39,7 @@ import io.reactivex.subjects.PublishSubject
import kotlinx.parcelize.Parcelize
import org.simple.clinic.R
import org.simple.clinic.ReportAnalyticsEvents
import org.simple.clinic.common.ui.theme.SimpleTheme
import org.simple.clinic.contactpatient.ContactPatientBottomSheet
import org.simple.clinic.databinding.ScreenPatientSummaryBinding
import org.simple.clinic.di.injector
Expand Down Expand Up @@ -61,6 +69,7 @@ import org.simple.clinic.remoteconfig.ConfigReader
import org.simple.clinic.scheduleappointment.ScheduleAppointmentSheet
import org.simple.clinic.scheduleappointment.facilityselection.FacilitySelectionScreen
import org.simple.clinic.summary.addphone.AddPhoneNumberDialog
import org.simple.clinic.summary.compose.StatinNudge
import org.simple.clinic.summary.linkId.LinkIdWithPatientSheet.LinkIdWithPatientSheetKey
import org.simple.clinic.summary.teleconsultation.contactdoctor.ContactDoctorSheet
import org.simple.clinic.summary.teleconsultation.messagebuilder.LongTeleconsultMessageBuilder_Old
Expand Down Expand Up @@ -168,11 +177,8 @@ class PatientSummaryScreen :
private val clinicalDecisionSupportAlertView
get() = binding.clinicalDecisionSupportBpHighAlert.rootView

private val statinAlertView
get() = binding.statinAlert.rootView

private val statinAlertDescription
get() = binding.statinAlert.statinAlertSubtitle
private val statinComposeView
get() = binding.statinComposeView

@Inject
lateinit var router: Router
Expand Down Expand Up @@ -210,6 +216,8 @@ class PatientSummaryScreen :

private val additionalEvents = DeferredEventSource<PatientSummaryEvent>()

private var shouldShowStatinNudge by mutableStateOf(false)

override fun defaultModel(): PatientSummaryModel {
return PatientSummaryModel.from(screenKey.intention, screenKey.patientUuid)
}
Expand Down Expand Up @@ -305,6 +313,18 @@ class PatientSummaryScreen :
rootLayout.hideKeyboard()

subscriptions.add(setupChildViewVisibility())

statinComposeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
SimpleTheme {
StatinNudge(
isVisible = shouldShowStatinNudge,
modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp)
)
}
}
}
}

private fun handleScreenResult(requestKey: Parcelable, result: Succeeded) {
Expand Down Expand Up @@ -741,39 +761,12 @@ class PatientSummaryScreen :
clinicalDecisionSupportAlertView.visibility = GONE
}

override fun showStatinAlert(statin: StatinModel) {
statinAlertDescription.text = buildString {
append("${getString(R.string.statin_alert_patient)} ")

if (statin.hasDiabetes) {
append(String.format(getString(R.string.statin_alert_has_diabetes), statin.age.toString()))

if (statin.hasHadHeartAttack.xor(statin.hasHadStroke)) {
append(" ${getString(R.string.statin_alert_and_seperator)} ")
}
}

when {
statin.hasHadHeartAttack && statin.hasHadStroke -> append(getCVDString(statin.hasDiabetes))
statin.hasHadHeartAttack -> append(getString(R.string.statin_alert_heart_attack))
statin.hasHadStroke -> append(getString(R.string.statin_alert_stroke))
}

append(".")
}
showWithAnimation(statinAlertView)
override fun showStatinAlert() {
shouldShowStatinNudge = true
}

override fun hideStatinAlert() {
hideWithAnimation(statinAlertView, R.id.tag_statin_alert_end_listener)
}

private fun getCVDString(hasDiabetes: Boolean): String {
return if (hasDiabetes) {
getString(R.string.statin_alert_cvd_with_diabetes)
} else {
getString(R.string.statin_alert_cvd)
}
shouldShowStatinNudge = false
}

private fun showWithAnimation(view: View) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ interface PatientSummaryScreenUi {
fun showClinicalDecisionSupportAlert()
fun hideClinicalDecisionSupportAlert()
fun hideClinicalDecisionSupportAlertWithoutAnimation()
fun showStatinAlert(statin: StatinModel)
fun showStatinAlert()
fun hideStatinAlert()
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,7 @@ class PatientSummaryUpdate(
hasStatinsPrescribedAlready.not() &&
isPatientEligibleForStatin

val updatedModel = model.updateStatinInfo(
StatinModel(
canPrescribeStatin = canPrescribeStatin,
age = event.age,
hasDiabetes = hasDiabetes,
hasHadStroke = hasHadStroke,
hasHadHeartAttack = hasHadHeartAttack,
)
)
val updatedModel = model.updateStatinInfo(canPrescribeStatin)
return next(updatedModel)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class PatientSummaryViewRenderer(
}

private fun renderClinicalDecisionBasedOnAppointment(model: PatientSummaryModel) {
if (model.statin?.canPrescribeStatin == true)
if (model.canPrescribeStatin == true)
return

if (model.hasScheduledAppointment) {
Expand Down Expand Up @@ -154,8 +154,8 @@ class PatientSummaryViewRenderer(
private fun renderStatinAlert(model: PatientSummaryModel) {
if (model.hasStatinInfoLoaded.not()) return

if (model.statin!!.canPrescribeStatin) {
ui.showStatinAlert(model.statin)
if (model.canPrescribeStatin == true) {
ui.showStatinAlert()
ui.hideClinicalDecisionSupportAlertWithoutAnimation()
} else {
ui.hideStatinAlert()
Expand Down
146 changes: 146 additions & 0 deletions app/src/main/java/org/simple/clinic/summary/compose/StatinNudgeView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.simple.clinic.summary.compose

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.simple.clinic.R
import org.simple.clinic.common.ui.theme.SimpleTheme

@Composable
fun StatinNudge(
isVisible: Boolean,
modifier: Modifier = Modifier
) {
AnimatedVisibility(
visible = isVisible,
) {
Card(
modifier = modifier
) {
Column(
modifier = Modifier
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row {
Box(
modifier = Modifier
.weight(2f)
)
Column(
modifier = Modifier
.weight(3f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier
.background(SimpleTheme.colors.material.error, shape = RoundedCornerShape(50))
.padding(horizontal = 8.dp, vertical = 4.dp),
style = SimpleTheme.typography.material.button,
color = SimpleTheme.colors.onToolbarPrimary,
text = stringResource(R.string.statin_alert_at_risk_patient)
)
}
}
Spacer(modifier = Modifier.height(4.dp))
RiskProgressBar()
Spacer(modifier = Modifier.height(16.dp))
Text(
text = buildAnnotatedString {
append(stringResource(R.string.statin_alert_refer_to_doctor_for))
append(" ")
withStyle(style = SimpleTheme.typography.body2Bold.toSpanStyle()) {
append(stringResource(R.string.statin_alert_statin_medicine))
}
},
color = SimpleTheme.colors.material.error,
style = SimpleTheme.typography.material.body2,
)
}
}
}
}

@Composable
fun RiskProgressBar() {
val riskColors = listOf(
Color(0xFF00B849), // Very Low
Color(0xFFFFC800), // Low
SimpleTheme.colors.material.error, // Medium
Color(0xFFB81631), // High
Color(0xFF731814) // Very High
)

val indicatorColor = Color(0xFF2F363D)

Box(
modifier = Modifier
.fillMaxWidth()
.height(14.dp)
.drawWithContent {
drawContent()

val widthPerSegment = size.width / riskColors.size

drawLine(
color = indicatorColor,
start = Offset(2 * widthPerSegment, 0f),
end = Offset(2 * widthPerSegment, size.height),
strokeWidth = 2.dp.toPx()
)
drawLine(
color = indicatorColor,
start = Offset(size.width, 0f),
end = Offset(size.width, size.height),
strokeWidth = 2.dp.toPx()
)
},
contentAlignment = Alignment.Center,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(4.dp)
.clip(RoundedCornerShape(50))
) {
riskColors.forEach { color ->
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.background(color),
)
}
}
}
}

@Preview
@Composable
fun StatinNudgePreview() {
SimpleTheme {
StatinNudge(true)
}
}
12 changes: 0 additions & 12 deletions app/src/main/res/drawable/background_statin_alert.xml

This file was deleted.

27 changes: 0 additions & 27 deletions app/src/main/res/drawable/ic_statin_alert.xml

This file was deleted.

Loading

0 comments on commit f0e820f

Please sign in to comment.