Skip to content

Commit b903403

Browse files
Merge pull request #5600 from simpledotorg/master
2 parents aa9da01 + 629bdad commit b903403

19 files changed

+300
-779
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
- Bump Sentry Android to v5.9.0
2828
- Bump AndroidX ViewModel to v2.9.3
2929
- Update Compose BOM to v2025.08.01
30+
- Migrate new medical history screen to Jetpack Compose
31+
- Bump AndroidX Benchmark to v1.4.0
32+
- Bump Open CSV to v5.12.0
3033

3134
### Changes
3235

app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffect.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ data object ShowHypertensionDiagnosisRequiredError : NewMedicalHistoryViewEffect
2626
data object ShowChangeDiagnosisErrorDialog : NewMedicalHistoryViewEffect()
2727

2828
data object ShowOngoingDiabetesTreatmentErrorDialog : NewMedicalHistoryViewEffect()
29+
30+
data object GoBack : NewMedicalHistoryViewEffect()

app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEvent.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ data class SyncTriggered(val registeredPatientUuid: UUID) : NewMedicalHistoryEve
3232
data object ChangeDiagnosisNotNowClicked : NewMedicalHistoryEvent() {
3333
override val analyticsName = "New Medical History:Change Diagnosis:Not Now Clicked"
3434
}
35+
36+
data object BackClicked : NewMedicalHistoryEvent() {
37+
override val analyticsName: String = "New Medical History:Back Clicked"
38+
}

app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt

Lines changed: 43 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,35 @@ import android.view.LayoutInflater
66
import android.view.View
77
import android.view.ViewGroup
88
import androidx.appcompat.app.AppCompatActivity
9-
import androidx.compose.foundation.layout.Arrangement
10-
import androidx.compose.foundation.layout.Column
11-
import androidx.compose.foundation.layout.fillMaxWidth
12-
import androidx.compose.foundation.layout.padding
139
import androidx.compose.runtime.getValue
14-
import androidx.compose.runtime.mutableStateOf
15-
import androidx.compose.runtime.setValue
16-
import androidx.compose.ui.Modifier
10+
import androidx.compose.runtime.livedata.observeAsState
11+
import androidx.compose.ui.platform.ComposeView
1712
import androidx.compose.ui.platform.ViewCompositionStrategy
18-
import androidx.compose.ui.res.dimensionResource
19-
import androidx.compose.ui.res.stringResource
13+
import androidx.fragment.app.Fragment
14+
import androidx.fragment.app.viewModels
2015
import com.google.android.material.dialog.MaterialAlertDialogBuilder
21-
import com.jakewharton.rxbinding3.view.clicks
22-
import com.spotify.mobius.functions.Consumer
23-
import io.reactivex.Observable
24-
import io.reactivex.rxkotlin.cast
25-
import io.reactivex.subjects.PublishSubject
26-
import io.reactivex.subjects.Subject
2716
import kotlinx.parcelize.Parcelize
2817
import org.simple.clinic.R
29-
import org.simple.clinic.ReportAnalyticsEvents
3018
import org.simple.clinic.appconfig.Country
31-
import org.simple.clinic.common.ui.theme.SimpleTheme
32-
import org.simple.clinic.databinding.ScreenNewMedicalHistoryBinding
3319
import org.simple.clinic.di.injector
34-
import org.simple.clinic.feature.Feature
3520
import org.simple.clinic.feature.Features
36-
import org.simple.clinic.medicalhistory.Answer
37-
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithDiabetes
38-
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithHypertension
39-
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment
40-
import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment
41-
import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry
4221
import org.simple.clinic.medicalhistory.SelectDiagnosisErrorDialog
4322
import org.simple.clinic.medicalhistory.SelectOngoingDiabetesTreatmentErrorDialog
4423
import org.simple.clinic.medicalhistory.SelectOngoingHypertensionTreatmentErrorDialog
45-
import org.simple.clinic.medicalhistory.ui.HistoryContainer
46-
import org.simple.clinic.medicalhistory.ui.MedicalHistoryDiagnosisWithTreatment
47-
import org.simple.clinic.medicalhistory.ui.TobaccoQuestion
24+
import org.simple.clinic.medicalhistory.ui.NewMedicalHistoryUi
25+
import org.simple.clinic.mobius.DisposableViewEffect
26+
import org.simple.clinic.navigation.v2.HandlesBack
4827
import org.simple.clinic.navigation.v2.Router
4928
import org.simple.clinic.navigation.v2.ScreenKey
50-
import org.simple.clinic.navigation.v2.fragments.BaseScreen
5129
import org.simple.clinic.summary.OpenIntention
5230
import org.simple.clinic.summary.PatientSummaryScreenKey
5331
import org.simple.clinic.util.UtcClock
54-
import org.simple.clinic.util.applyInsetsBottomPadding
55-
import org.simple.clinic.util.applyStatusBarPadding
56-
import org.simple.clinic.widgets.ProgressMaterialButton.ButtonState.Enabled
57-
import org.simple.clinic.widgets.ProgressMaterialButton.ButtonState.InProgress
32+
import org.simple.clinic.util.unsafeLazy
5833
import java.time.Instant
5934
import java.util.UUID
6035
import javax.inject.Inject
6136

62-
class NewMedicalHistoryScreen : BaseScreen<
63-
NewMedicalHistoryScreen.Key,
64-
ScreenNewMedicalHistoryBinding,
65-
NewMedicalHistoryModel,
66-
NewMedicalHistoryEvent,
67-
NewMedicalHistoryEffect,
68-
NewMedicalHistoryViewEffect>(), NewMedicalHistoryUi, NewMedicalHistoryUiActions {
37+
class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesBack {
6938

7039
@Inject
7140
lateinit var router: Router
@@ -85,210 +54,52 @@ class NewMedicalHistoryScreen : BaseScreen<
8554
@Inject
8655
lateinit var features: Features
8756

88-
private val appbar
89-
get() = binding.appbar
90-
91-
private val toolbar
92-
get() = binding.toolbar
93-
94-
private val nextButtonFrame
95-
get() = binding.nextButtonFrame
96-
97-
private val nextButton
98-
get() = binding.nextButton
99-
100-
private var showSmokerQuestion by mutableStateOf(false)
101-
private var showSmokelessTobaccoQuestion by mutableStateOf(false)
102-
private var showDiabetesQuestion by mutableStateOf(false)
103-
private var showHypertensionTreatmentQuestion by mutableStateOf(false)
104-
private var showDiabetesTreatmentQuestion by mutableStateOf(false)
105-
private var showDiabetesDiagnosis by mutableStateOf(false)
106-
private var ongoingMedicalHistoryEntry by mutableStateOf<OngoingMedicalHistoryEntry?>(null)
107-
108-
private val composeView
109-
get() = binding.composeView
57+
@Inject
58+
lateinit var newMedicalHistoryEffectHandler: NewMedicalHistoryEffectHandler.Factory
11059

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

113-
override fun defaultModel() = NewMedicalHistoryModel.default(
114-
country = country,
115-
showIsSmokingQuestion = features.isEnabled(Feature.NonLabBasedStatinNudge) ||
116-
features.isEnabled(Feature.LabBasedStatinNudge),
117-
showSmokelessTobaccoQuestion = country.isoCountryCode != Country.ETHIOPIA
62+
private val viewModel by viewModels<NewMedicalHistoryViewModel>(
63+
factoryProducer = {
64+
NewMedicalHistoryViewModel.factory(
65+
country = country,
66+
features = features,
67+
effectHandlerFactory = effectHandlerFactory)
68+
}
11869
)
11970

120-
override fun bindView(
121-
layoutInflater: LayoutInflater,
122-
container: ViewGroup?
123-
) = ScreenNewMedicalHistoryBinding.inflate(layoutInflater, container, false)
124-
125-
override fun createUpdate() = NewMedicalHistoryUpdate()
126-
127-
override fun events() = Observable
128-
.merge(
129-
hotEvents,
130-
saveClicks()
131-
)
132-
.compose(ReportAnalyticsEvents())
133-
.cast<NewMedicalHistoryEvent>()
134-
135-
override fun createEffectHandler(
136-
viewEffectsConsumer: Consumer<NewMedicalHistoryViewEffect>
137-
) = effectHandlerFactory
138-
.create(
139-
viewEffectsConsumer = viewEffectsConsumer
140-
)
141-
.build()
142-
143-
override fun createInit() = NewMedicalHistoryInit()
144-
145-
override fun uiRenderer() = NewMedicalHistoryUiRenderer(this)
146-
147-
override fun viewEffectHandler() = NewMedicalHistoryViewEffectHandler(this)
148-
14971
override fun onAttach(context: Context) {
15072
super.onAttach(context)
15173
context.injector<Injector>().inject(this)
15274
}
15375

154-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
155-
super.onViewCreated(view, savedInstanceState)
156-
appbar.applyStatusBarPadding()
157-
nextButtonFrame.applyInsetsBottomPadding()
158-
toolbar.setNavigationOnClickListener {
159-
router.pop()
160-
}
76+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
77+
return ComposeView(requireContext()).apply {
78+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
16179

162-
composeView.apply {
163-
setViewCompositionStrategy(
164-
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
165-
)
16680
setContent {
167-
SimpleTheme {
168-
Column(
169-
modifier = Modifier
170-
.fillMaxWidth()
171-
.padding(dimensionResource(R.dimen.spacing_8)),
172-
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing_8))
173-
) {
174-
MedicalHistoryDiagnosisWithTreatment(
175-
diagnosisLabel = stringResource(R.string.medicalhistory_diagnosis_hypertension_required),
176-
diagnosisQuestion = DiagnosedWithHypertension,
177-
diagnosisAnswer = ongoingMedicalHistoryEntry?.diagnosedWithHypertension,
178-
treatmentQuestion = IsOnHypertensionTreatment(country.isoCountryCode),
179-
treatmentAnswer = ongoingMedicalHistoryEntry?.isOnHypertensionTreatment,
180-
showTreatmentQuestion = showHypertensionTreatmentQuestion
181-
) { question, answer ->
182-
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
183-
}
184-
if (showDiabetesDiagnosis) {
185-
MedicalHistoryDiagnosisWithTreatment(
186-
diagnosisLabel = stringResource(R.string.medicalhistory_diagnosis_diabetes_required),
187-
diagnosisQuestion = DiagnosedWithDiabetes,
188-
diagnosisAnswer = ongoingMedicalHistoryEntry?.hasDiabetes,
189-
treatmentQuestion = IsOnDiabetesTreatment,
190-
treatmentAnswer = ongoingMedicalHistoryEntry?.isOnDiabetesTreatment,
191-
showTreatmentQuestion = showDiabetesTreatmentQuestion
192-
) { question, answer ->
193-
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
194-
}
195-
}
196-
HistoryContainer(
197-
heartAttackAnswer = ongoingMedicalHistoryEntry?.hasHadHeartAttack,
198-
strokeAnswer = ongoingMedicalHistoryEntry?.hasHadStroke,
199-
kidneyAnswer = ongoingMedicalHistoryEntry?.hasHadKidneyDisease,
200-
diabetesAnswer = ongoingMedicalHistoryEntry?.hasDiabetes,
201-
showDiabetesQuestion = showDiabetesQuestion,
202-
) { question, answer ->
203-
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
204-
}
205-
if (showSmokerQuestion) {
206-
TobaccoQuestion(
207-
isSmokingAnswer = ongoingMedicalHistoryEntry?.isSmoking,
208-
isUsingSmokelessTobaccoAnswer = ongoingMedicalHistoryEntry?.isUsingSmokelessTobacco,
209-
showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion,
210-
) { question, answer ->
211-
hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer))
81+
viewModel.viewEffects.DisposableViewEffect(viewEffectHandler::handle)
82+
83+
val model by viewModel.models.observeAsState()
84+
model?.let {
85+
NewMedicalHistoryUi(
86+
model = it,
87+
navigationIconClick = { onBackPressed() },
88+
onNextClick = {
89+
viewModel.dispatch(SaveMedicalHistoryClicked())
21290
}
213-
}
91+
) { question, answer ->
92+
viewModel.dispatch(NewMedicalHistoryAnswerToggled(question, answer))
21493
}
21594
}
21695
}
21796
}
21897
}
21998

220-
private fun saveClicks() = nextButton
221-
.clicks()
222-
.map { SaveMedicalHistoryClicked() }
223-
22499
override fun openPatientSummaryScreen(patientUuid: UUID) {
225100
router.push(PatientSummaryScreenKey(patientUuid, OpenIntention.ViewNewPatient, Instant.now(utcClock)))
226101
}
227102

228-
override fun setPatientName(patientName: String) {
229-
toolbar.title = patientName
230-
}
231-
232-
override fun populateOngoingMedicalHistoryEntry(ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry) {
233-
this.ongoingMedicalHistoryEntry = ongoingMedicalHistoryEntry
234-
}
235-
236-
override fun showDiabetesDiagnosisView() {
237-
showDiabetesDiagnosis = true
238-
}
239-
240-
override fun hideDiabetesDiagnosisView() {
241-
showDiabetesDiagnosis = false
242-
}
243-
244-
override fun hideDiabetesHistorySection() {
245-
showDiabetesQuestion = false
246-
}
247-
248-
override fun showDiabetesHistorySection() {
249-
showDiabetesQuestion = true
250-
}
251-
252-
override fun showNextButtonProgress() {
253-
nextButton.setButtonState(InProgress)
254-
}
255-
256-
override fun hideNextButtonProgress() {
257-
nextButton.setButtonState(Enabled)
258-
}
259-
260-
override fun showHypertensionTreatmentQuestion(answer: Answer) {
261-
showHypertensionTreatmentQuestion = true
262-
}
263-
264-
override fun hideHypertensionTreatmentQuestion() {
265-
showHypertensionTreatmentQuestion = false
266-
}
267-
268-
override fun showDiabetesTreatmentQuestion(answer: Answer) {
269-
showDiabetesTreatmentQuestion = true
270-
}
271-
272-
override fun hideDiabetesTreatmentQuestion() {
273-
showDiabetesTreatmentQuestion = false
274-
}
275-
276-
override fun showCurrentSmokerQuestion() {
277-
showSmokerQuestion = true
278-
}
279-
280-
override fun hideCurrentSmokerQuestion() {
281-
showSmokerQuestion = false
282-
}
283-
284-
override fun showSmokelessTobaccoQuestion() {
285-
showSmokelessTobaccoQuestion = true
286-
}
287-
288-
override fun hideSmokelessTobaccoQuestion() {
289-
showSmokelessTobaccoQuestion = false
290-
}
291-
292103
override fun showOngoingHypertensionTreatmentErrorDialog() {
293104
SelectOngoingHypertensionTreatmentErrorDialog.show(fragmentManager = activity.supportFragmentManager)
294105
}
@@ -297,6 +108,10 @@ class NewMedicalHistoryScreen : BaseScreen<
297108
SelectOngoingDiabetesTreatmentErrorDialog.show(fragmentManager = activity.supportFragmentManager)
298109
}
299110

111+
override fun goBack() {
112+
router.pop()
113+
}
114+
300115
override fun showDiagnosisRequiredErrorDialog() {
301116
SelectDiagnosisErrorDialog.show(activity.supportFragmentManager)
302117
}
@@ -315,11 +130,16 @@ class NewMedicalHistoryScreen : BaseScreen<
315130
.setMessage(getString(R.string.change_diagnosis_message))
316131
.setPositiveButton(getString(R.string.change_diagnosis_positive), null)
317132
.setNegativeButton(getString(R.string.change_diagnosis_negative)) { _, _ ->
318-
hotEvents.onNext(ChangeDiagnosisNotNowClicked)
133+
viewModel.dispatch(ChangeDiagnosisNotNowClicked)
319134
}
320135
.show()
321136
}
322137

138+
override fun onBackPressed(): Boolean {
139+
viewModel.dispatch(BackClicked)
140+
return true
141+
}
142+
323143
interface Injector {
324144
fun inject(target: NewMedicalHistoryScreen)
325145
}

app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUi.kt

Lines changed: 0 additions & 24 deletions
This file was deleted.

app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiActions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ interface NewMedicalHistoryUiActions {
99
fun showHypertensionDiagnosisRequiredErrorDialog()
1010
fun showChangeDiagnosisErrorDialog()
1111
fun showOngoingDiabetesTreatmentErrorDialog()
12+
13+
fun goBack()
1214
}

0 commit comments

Comments
 (0)