Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- Bump Sentry Android to v5.9.0
- Bump AndroidX ViewModel to v2.9.3
- Update Compose BOM to v2025.08.01
- Migrate new medical history screen to Jetpack Compose

### Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ data object ShowHypertensionDiagnosisRequiredError : NewMedicalHistoryViewEffect
data object ShowChangeDiagnosisErrorDialog : NewMedicalHistoryViewEffect()

data object ShowOngoingDiabetesTreatmentErrorDialog : NewMedicalHistoryViewEffect()

data object GoBack : NewMedicalHistoryViewEffect()
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ data class SyncTriggered(val registeredPatientUuid: UUID) : NewMedicalHistoryEve
data object ChangeDiagnosisNotNowClicked : NewMedicalHistoryEvent() {
override val analyticsName = "New Medical History:Change Diagnosis:Not Now Clicked"
}

data object BackClicked : NewMedicalHistoryEvent() {
override val analyticsName: String = "New Medical History:Back Clicked"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,35 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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.runtime.livedata.observeAsState
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.view.clicks
import com.spotify.mobius.functions.Consumer
import io.reactivex.Observable
import io.reactivex.rxkotlin.cast
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import kotlinx.parcelize.Parcelize
import org.simple.clinic.R
import org.simple.clinic.ReportAnalyticsEvents
import org.simple.clinic.appconfig.Country
import org.simple.clinic.common.ui.theme.SimpleTheme
import org.simple.clinic.databinding.ScreenNewMedicalHistoryBinding
import org.simple.clinic.di.injector
import org.simple.clinic.feature.Feature
import org.simple.clinic.feature.Features
import org.simple.clinic.medicalhistory.Answer
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithDiabetes
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithHypertension
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment
import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry
import org.simple.clinic.medicalhistory.SelectDiagnosisErrorDialog
import org.simple.clinic.medicalhistory.SelectOngoingDiabetesTreatmentErrorDialog
import org.simple.clinic.medicalhistory.SelectOngoingHypertensionTreatmentErrorDialog
import org.simple.clinic.medicalhistory.ui.HistoryContainer
import org.simple.clinic.medicalhistory.ui.MedicalHistoryDiagnosisWithTreatment
import org.simple.clinic.medicalhistory.ui.TobaccoQuestion
import org.simple.clinic.medicalhistory.ui.NewMedicalHistoryUi
import org.simple.clinic.mobius.DisposableViewEffect
import org.simple.clinic.navigation.v2.HandlesBack
import org.simple.clinic.navigation.v2.Router
import org.simple.clinic.navigation.v2.ScreenKey
import org.simple.clinic.navigation.v2.fragments.BaseScreen
import org.simple.clinic.summary.OpenIntention
import org.simple.clinic.summary.PatientSummaryScreenKey
import org.simple.clinic.util.UtcClock
import org.simple.clinic.util.applyInsetsBottomPadding
import org.simple.clinic.util.applyStatusBarPadding
import org.simple.clinic.widgets.ProgressMaterialButton.ButtonState.Enabled
import org.simple.clinic.widgets.ProgressMaterialButton.ButtonState.InProgress
import org.simple.clinic.util.unsafeLazy
import java.time.Instant
import java.util.UUID
import javax.inject.Inject

class NewMedicalHistoryScreen : BaseScreen<
NewMedicalHistoryScreen.Key,
ScreenNewMedicalHistoryBinding,
NewMedicalHistoryModel,
NewMedicalHistoryEvent,
NewMedicalHistoryEffect,
NewMedicalHistoryViewEffect>(), NewMedicalHistoryUi, NewMedicalHistoryUiActions {
class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesBack {

@Inject
lateinit var router: Router
Expand All @@ -85,210 +54,52 @@ class NewMedicalHistoryScreen : BaseScreen<
@Inject
lateinit var features: Features

private val appbar
get() = binding.appbar

private val toolbar
get() = binding.toolbar

private val nextButtonFrame
get() = binding.nextButtonFrame

private val nextButton
get() = binding.nextButton

private var showSmokerQuestion by mutableStateOf(false)
private var showSmokelessTobaccoQuestion by mutableStateOf(false)
private var showDiabetesQuestion by mutableStateOf(false)
private var showHypertensionTreatmentQuestion by mutableStateOf(false)
private var showDiabetesTreatmentQuestion by mutableStateOf(false)
private var showDiabetesDiagnosis by mutableStateOf(false)
private var ongoingMedicalHistoryEntry by mutableStateOf<OngoingMedicalHistoryEntry?>(null)

private val composeView
get() = binding.composeView
@Inject
lateinit var newMedicalHistoryEffectHandler: NewMedicalHistoryEffectHandler.Factory

private val hotEvents: Subject<NewMedicalHistoryEvent> = PublishSubject.create()
private val viewEffectHandler by unsafeLazy { NewMedicalHistoryViewEffectHandler(this) }

override fun defaultModel() = NewMedicalHistoryModel.default(
country = country,
showIsSmokingQuestion = features.isEnabled(Feature.NonLabBasedStatinNudge) ||
features.isEnabled(Feature.LabBasedStatinNudge),
showSmokelessTobaccoQuestion = country.isoCountryCode != Country.ETHIOPIA
private val viewModel by viewModels<NewMedicalHistoryViewModel>(
factoryProducer = {
NewMedicalHistoryViewModel.factory(
country = country,
features = features,
effectHandlerFactory = effectHandlerFactory)
}
)

override fun bindView(
layoutInflater: LayoutInflater,
container: ViewGroup?
) = ScreenNewMedicalHistoryBinding.inflate(layoutInflater, container, false)

override fun createUpdate() = NewMedicalHistoryUpdate()

override fun events() = Observable
.merge(
hotEvents,
saveClicks()
)
.compose(ReportAnalyticsEvents())
.cast<NewMedicalHistoryEvent>()

override fun createEffectHandler(
viewEffectsConsumer: Consumer<NewMedicalHistoryViewEffect>
) = effectHandlerFactory
.create(
viewEffectsConsumer = viewEffectsConsumer
)
.build()

override fun createInit() = NewMedicalHistoryInit()

override fun uiRenderer() = NewMedicalHistoryUiRenderer(this)

override fun viewEffectHandler() = NewMedicalHistoryViewEffectHandler(this)

override fun onAttach(context: Context) {
super.onAttach(context)
context.injector<Injector>().inject(this)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
appbar.applyStatusBarPadding()
nextButtonFrame.applyInsetsBottomPadding()
toolbar.setNavigationOnClickListener {
router.pop()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)

composeView.apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
SimpleTheme {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.spacing_8)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing_8))
) {
MedicalHistoryDiagnosisWithTreatment(
diagnosisLabel = stringResource(R.string.medicalhistory_diagnosis_hypertension_required),
diagnosisQuestion = DiagnosedWithHypertension,
diagnosisAnswer = ongoingMedicalHistoryEntry?.diagnosedWithHypertension,
treatmentQuestion = IsOnHypertensionTreatment(country.isoCountryCode),
treatmentAnswer = ongoingMedicalHistoryEntry?.isOnHypertensionTreatment,
showTreatmentQuestion = showHypertensionTreatmentQuestion
) { question, answer ->
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
}
if (showDiabetesDiagnosis) {
MedicalHistoryDiagnosisWithTreatment(
diagnosisLabel = stringResource(R.string.medicalhistory_diagnosis_diabetes_required),
diagnosisQuestion = DiagnosedWithDiabetes,
diagnosisAnswer = ongoingMedicalHistoryEntry?.hasDiabetes,
treatmentQuestion = IsOnDiabetesTreatment,
treatmentAnswer = ongoingMedicalHistoryEntry?.isOnDiabetesTreatment,
showTreatmentQuestion = showDiabetesTreatmentQuestion
) { question, answer ->
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
}
}
HistoryContainer(
heartAttackAnswer = ongoingMedicalHistoryEntry?.hasHadHeartAttack,
strokeAnswer = ongoingMedicalHistoryEntry?.hasHadStroke,
kidneyAnswer = ongoingMedicalHistoryEntry?.hasHadKidneyDisease,
diabetesAnswer = ongoingMedicalHistoryEntry?.hasDiabetes,
showDiabetesQuestion = showDiabetesQuestion,
) { question, answer ->
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
}
if (showSmokerQuestion) {
TobaccoQuestion(
isSmokingAnswer = ongoingMedicalHistoryEntry?.isSmoking,
isUsingSmokelessTobaccoAnswer = ongoingMedicalHistoryEntry?.isUsingSmokelessTobacco,
showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion,
) { question, answer ->
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
viewModel.viewEffects.DisposableViewEffect(viewEffectHandler::handle)

val model by viewModel.models.observeAsState()
model?.let {
NewMedicalHistoryUi(
model = it,
navigationIconClick = { onBackPressed() },
onNextClick = {
viewModel.dispatch(SaveMedicalHistoryClicked())
}
}
) { question, answer ->
viewModel.dispatch(NewMedicalHistoryAnswerToggled(question, answer))
}
}
}
}
}

private fun saveClicks() = nextButton
.clicks()
.map { SaveMedicalHistoryClicked() }

override fun openPatientSummaryScreen(patientUuid: UUID) {
router.push(PatientSummaryScreenKey(patientUuid, OpenIntention.ViewNewPatient, Instant.now(utcClock)))
}

override fun setPatientName(patientName: String) {
toolbar.title = patientName
}

override fun populateOngoingMedicalHistoryEntry(ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry) {
this.ongoingMedicalHistoryEntry = ongoingMedicalHistoryEntry
}

override fun showDiabetesDiagnosisView() {
showDiabetesDiagnosis = true
}

override fun hideDiabetesDiagnosisView() {
showDiabetesDiagnosis = false
}

override fun hideDiabetesHistorySection() {
showDiabetesQuestion = false
}

override fun showDiabetesHistorySection() {
showDiabetesQuestion = true
}

override fun showNextButtonProgress() {
nextButton.setButtonState(InProgress)
}

override fun hideNextButtonProgress() {
nextButton.setButtonState(Enabled)
}

override fun showHypertensionTreatmentQuestion(answer: Answer) {
showHypertensionTreatmentQuestion = true
}

override fun hideHypertensionTreatmentQuestion() {
showHypertensionTreatmentQuestion = false
}

override fun showDiabetesTreatmentQuestion(answer: Answer) {
showDiabetesTreatmentQuestion = true
}

override fun hideDiabetesTreatmentQuestion() {
showDiabetesTreatmentQuestion = false
}

override fun showCurrentSmokerQuestion() {
showSmokerQuestion = true
}

override fun hideCurrentSmokerQuestion() {
showSmokerQuestion = false
}

override fun showSmokelessTobaccoQuestion() {
showSmokelessTobaccoQuestion = true
}

override fun hideSmokelessTobaccoQuestion() {
showSmokelessTobaccoQuestion = false
}

override fun showOngoingHypertensionTreatmentErrorDialog() {
SelectOngoingHypertensionTreatmentErrorDialog.show(fragmentManager = activity.supportFragmentManager)
}
Expand All @@ -297,6 +108,10 @@ class NewMedicalHistoryScreen : BaseScreen<
SelectOngoingDiabetesTreatmentErrorDialog.show(fragmentManager = activity.supportFragmentManager)
}

override fun goBack() {
router.pop()
}

override fun showDiagnosisRequiredErrorDialog() {
SelectDiagnosisErrorDialog.show(activity.supportFragmentManager)
}
Expand All @@ -315,11 +130,16 @@ class NewMedicalHistoryScreen : BaseScreen<
.setMessage(getString(R.string.change_diagnosis_message))
.setPositiveButton(getString(R.string.change_diagnosis_positive), null)
.setNegativeButton(getString(R.string.change_diagnosis_negative)) { _, _ ->
hotEvents.onNext(ChangeDiagnosisNotNowClicked)
viewModel.dispatch(ChangeDiagnosisNotNowClicked)
}
.show()
}

override fun onBackPressed(): Boolean {
viewModel.dispatch(BackClicked)
return true
}

interface Injector {
fun inject(target: NewMedicalHistoryScreen)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface NewMedicalHistoryUiActions {
fun showHypertensionDiagnosisRequiredErrorDialog()
fun showChangeDiagnosisErrorDialog()
fun showOngoingDiabetesTreatmentErrorDialog()

fun goBack()
}
Loading