diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b1a9cf5aa..b7d0a5c0eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Changes - Update Titration nudge threshold for diabetic patients in SriLanka from `140/90` to `130/80` +- Add BMI container to patient registration screen ## 2026.05.11 diff --git a/app/src/main/java/org/simple/clinic/feature/Feature.kt b/app/src/main/java/org/simple/clinic/feature/Feature.kt index 77666331dd0..7b9bf9733a9 100644 --- a/app/src/main/java/org/simple/clinic/feature/Feature.kt +++ b/app/src/main/java/org/simple/clinic/feature/Feature.kt @@ -30,4 +30,5 @@ enum class Feature( ShowDiagnosisButton(false, "show_diagnosis_button"), SortOverdueBasedOnReturnScore(false, "sort_overdue_based_on_return_score"), ShowReturnScoreDebugValues(false, "show_return_score_debug_values"), + ShowBMIContainer(false, "show_bmi_container"), } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffect.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffect.kt index 200e05439dc..b2d4e38c3d1 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffect.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffect.kt @@ -1,11 +1,15 @@ package org.simple.clinic.medicalhistory.newentry import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry +import org.simple.clinic.patientattribute.BMIReading import java.util.UUID sealed class NewMedicalHistoryEffect -data class RegisterPatient(val ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry) : NewMedicalHistoryEffect() +data class RegisterPatient( + val ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry, + val bmiReading: BMIReading? +) : NewMedicalHistoryEffect() data object LoadOngoingPatientEntry : NewMedicalHistoryEffect() @@ -31,4 +35,8 @@ data object ShowChangeDiagnosisErrorDialog : NewMedicalHistoryViewEffect() data object ShowOngoingDiabetesTreatmentErrorDialog : NewMedicalHistoryViewEffect() +data class OpenBMIEntrySheet( + val bmiReading: BMIReading? +) : NewMedicalHistoryViewEffect() + data object GoBack : NewMedicalHistoryViewEffect() diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandler.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandler.kt index ef12b05aed8..5ed0147e92b 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandler.kt @@ -6,12 +6,15 @@ import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.reactivex.Completable import io.reactivex.ObservableTransformer import io.reactivex.Scheduler import org.simple.clinic.facility.Facility import org.simple.clinic.medicalhistory.MedicalHistoryRepository import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry import org.simple.clinic.patient.PatientRepository +import org.simple.clinic.patientattribute.BMIReading +import org.simple.clinic.patientattribute.PatientAttributeRepository import org.simple.clinic.sync.DataSync import org.simple.clinic.user.User import org.simple.clinic.util.scheduler.SchedulersProvider @@ -23,6 +26,7 @@ class NewMedicalHistoryEffectHandler @AssistedInject constructor( private val schedulersProvider: SchedulersProvider, private val patientRepository: PatientRepository, private val medicalHistoryRepository: MedicalHistoryRepository, + private val patientAttributeRepository: PatientAttributeRepository, private val dataSync: DataSync, private val currentUser: Lazy, private val currentFacility: Lazy, @@ -57,30 +61,44 @@ class NewMedicalHistoryEffectHandler @AssistedInject constructor( val loggedInUser = currentUser.get() val facility = currentFacility.get() val ongoingMedicalHistoryEntry = registerPatientEffect.ongoingMedicalHistoryEntry + val bmiReading = registerPatientEffect.bmiReading - RegisterPatientData(loggedInUser, facility, ongoingMedicalHistoryEntry) + RegisterPatientData(loggedInUser, facility, ongoingMedicalHistoryEntry, bmiReading) } - .map { (user, facility, ongoingMedicalHistoryEntry) -> + .map { registerPatientData -> patientRepository .saveOngoingEntryAsPatient( patientEntry = patientRepository.ongoingEntry(), - loggedInUser = user, - facility = facility, + loggedInUser = registerPatientData.user, + facility = registerPatientData.facility, patientUuid = uuidGenerator.v4(), addressUuid = uuidGenerator.v4(), supplyUuidForBpPassport = uuidGenerator::v4, supplyUuidForAlternativeId = uuidGenerator::v4, supplyUuidForPhoneNumber = uuidGenerator::v4, dateOfBirthFormatter = dateOfBirthFormatter - ) to ongoingMedicalHistoryEntry + ) to registerPatientData } - .flatMapSingle { (registeredPatient, ongoingMedicalHistoryEntry) -> + .flatMapSingle { (registeredPatient, registeredPatientData) -> + + val saveBmi = Completable.fromAction { + registeredPatientData.bmiReading?.let { + patientAttributeRepository.save( + bmiReading = it, + patientUuid = registeredPatient.patientUuid, + loggedInUserUuid = registeredPatientData.user.uuid, + uuid = uuidGenerator.v4(), + ) + } + } + medicalHistoryRepository .save( uuid = uuidGenerator.v4(), patientUuid = registeredPatient.patientUuid, - historyEntry = ongoingMedicalHistoryEntry + historyEntry = registeredPatientData.ongoingMedicalHistoryEntry ) + .andThen(saveBmi) .toSingleDefault(PatientRegistered(registeredPatient.patientUuid)) } } @@ -114,6 +132,7 @@ class NewMedicalHistoryEffectHandler @AssistedInject constructor( private data class RegisterPatientData( val user: User, val facility: Facility, - val ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry + val ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry, + val bmiReading: BMIReading? ) } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEvent.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEvent.kt index 88c6b492738..dfbc5d0e49b 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEvent.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEvent.kt @@ -4,6 +4,7 @@ import org.simple.clinic.facility.Facility import org.simple.clinic.medicalhistory.Answer import org.simple.clinic.medicalhistory.MedicalHistoryQuestion import org.simple.clinic.patient.OngoingNewPatientEntry +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.widgets.UiEvent import java.util.UUID @@ -29,6 +30,10 @@ data class CurrentFacilityLoaded(val facility: Facility) : NewMedicalHistoryEven data class SyncTriggered(val registeredPatientUuid: UUID) : NewMedicalHistoryEvent() +data class BMIReadingAdded( + val bmiReading: BMIReading +) : NewMedicalHistoryEvent() + data object ChangeDiagnosisNotNowClicked : NewMedicalHistoryEvent() { override val analyticsName = "New Medical History:Change Diagnosis:Not Now Clicked" } @@ -36,3 +41,8 @@ data object ChangeDiagnosisNotNowClicked : NewMedicalHistoryEvent() { data object BackClicked : NewMedicalHistoryEvent() { override val analyticsName: String = "New Medical History:Back Clicked" } + +data object AddOrEditBMIClicked : NewMedicalHistoryEvent() { + override val analyticsName: String = "New Medical History:Add BMI Clicked" +} + diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt index 40973004f6d..cac580327eb 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt @@ -11,18 +11,21 @@ import org.simple.clinic.medicalhistory.Answer.Yes import org.simple.clinic.medicalhistory.MedicalHistoryQuestion import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry import org.simple.clinic.patient.OngoingNewPatientEntry +import org.simple.clinic.patientattribute.BMIReading @Parcelize data class NewMedicalHistoryModel( val country: Country, val ongoingPatientEntry: OngoingNewPatientEntry?, val ongoingMedicalHistoryEntry: OngoingMedicalHistoryEntry, + val bmiReading: BMIReading?, val currentFacility: Facility?, val nextButtonState: ButtonState?, val hasShownChangeDiagnosisError: Boolean, val showIsSmokingQuestion: Boolean, val showSmokelessTobaccoQuestion: Boolean, val isScreeningFeatureEnabled: Boolean, + val showBMIContainer: Boolean, ) : Parcelable { val hasLoadedPatientEntry: Boolean @@ -79,16 +82,19 @@ data class NewMedicalHistoryModel( showIsSmokingQuestion: Boolean, showSmokelessTobaccoQuestion: Boolean, isScreeningFeatureEnabled: Boolean, + showBMIContainer: Boolean, ): NewMedicalHistoryModel = NewMedicalHistoryModel( country = country, ongoingPatientEntry = null, ongoingMedicalHistoryEntry = OngoingMedicalHistoryEntry(), + bmiReading = null, currentFacility = null, nextButtonState = null, hasShownChangeDiagnosisError = false, showIsSmokingQuestion = showIsSmokingQuestion, showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion, isScreeningFeatureEnabled = isScreeningFeatureEnabled, + showBMIContainer = showBMIContainer, ) } @@ -115,4 +121,9 @@ data class NewMedicalHistoryModel( fun changeDiagnosisErrorShown(): NewMedicalHistoryModel { return copy(hasShownChangeDiagnosisError = true) } + + fun changeBMIReading(bmiReading: BMIReading?): NewMedicalHistoryModel { + return copy(bmiReading = bmiReading) + } + } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt index a288bfb9038..47110e050be 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt @@ -2,6 +2,7 @@ package org.simple.clinic.medicalhistory.newentry import android.content.Context import android.os.Bundle +import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,9 +30,14 @@ 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.Succeeded +import org.simple.clinic.patientattribute.BMIReading +import org.simple.clinic.patientattribute.entry.BMIEntrySheet import org.simple.clinic.summary.OpenIntention +import org.simple.clinic.summary.PatientSummaryScreen.ScreenRequest import org.simple.clinic.summary.PatientSummaryScreenKey import org.simple.clinic.util.UtcClock +import org.simple.clinic.util.setFragmentResultListener import org.simple.clinic.util.unsafeLazy import java.time.Instant import java.util.UUID @@ -73,6 +79,17 @@ class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesB context.injector().inject(this) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setFragmentResultListener( + ScreenRequest.BMIEntrySheet + ) { requestKey, result -> + if (result is Succeeded) { + handleScreenResult(requestKey, result) + } + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) @@ -87,7 +104,11 @@ class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesB navigationIconClick = { onBackPressed() }, onNextClick = { viewModel.dispatch(SaveMedicalHistoryClicked()) + }, + onAddBMIClick = { + viewModel.dispatch(AddOrEditBMIClicked) } + ) { question, answer -> viewModel.dispatch(NewMedicalHistoryAnswerToggled(question, answer)) } @@ -96,6 +117,18 @@ class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesB } } + private fun handleScreenResult(requestKey: Parcelable, result: Succeeded) { + when (requestKey) { + is ScreenRequest.BMIEntrySheet -> { + val bmiReading = (result.result as BMIEntrySheet.BMIAdded).bmiReading + + if (bmiReading != null) { + viewModel.dispatch(BMIReadingAdded(bmiReading)) + } + } + } + } + override fun openPatientSummaryScreen(patientUuid: UUID) { router.push(PatientSummaryScreenKey(patientUuid, OpenIntention.ViewNewPatient, Instant.now(utcClock))) } @@ -108,6 +141,10 @@ class NewMedicalHistoryScreen : Fragment(), NewMedicalHistoryUiActions, HandlesB SelectOngoingDiabetesTreatmentErrorDialog.show(fragmentManager = activity.supportFragmentManager) } + override fun openBMIEntrySheet(bmiReading: BMIReading?) { + router.pushExpectingResult(ScreenRequest.BMIEntrySheet, BMIEntrySheet.Key(bmiReading)) + } + override fun goBack() { router.pop() } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiActions.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiActions.kt index 4ea406e5025..0b078182977 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiActions.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiActions.kt @@ -1,5 +1,6 @@ package org.simple.clinic.medicalhistory.newentry +import org.simple.clinic.patientattribute.BMIReading import java.util.UUID interface NewMedicalHistoryUiActions { @@ -13,5 +14,6 @@ interface NewMedicalHistoryUiActions { fun showChangeDiagnosisErrorDialog() fun showOngoingDiabetesTreatmentErrorDialog() + fun openBMIEntrySheet(bmiReading: BMIReading?) fun goBack() } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdate.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdate.kt index ed6121bd6ee..6b26744f44b 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdate.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdate.kt @@ -23,6 +23,8 @@ class NewMedicalHistoryUpdate : Update dispatch(OpenPatientSummaryScreen(event.registeredPatientUuid)) is ChangeDiagnosisNotNowClicked -> registerPatient(model) is BackClicked -> dispatch(GoBack) + is AddOrEditBMIClicked -> dispatch(OpenBMIEntrySheet(model.bmiReading)) + is BMIReadingAdded -> next(model.changeBMIReading(event.bmiReading)) } } @@ -72,7 +74,7 @@ class NewMedicalHistoryUpdate : Update uiActions.showHypertensionDiagnosisRequiredOrReferralErrorDialog() ShowChangeDiagnosisErrorDialog -> uiActions.showChangeDiagnosisErrorDialog() ShowOngoingDiabetesTreatmentErrorDialog -> uiActions.showOngoingDiabetesTreatmentErrorDialog() + is OpenBMIEntrySheet -> uiActions.openBMIEntrySheet(viewEffect.bmiReading) GoBack -> uiActions.goBack() }.exhaustive() } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryViewModel.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryViewModel.kt index 3368bc085dc..2e0ef2b98aa 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryViewModel.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryViewModel.kt @@ -24,6 +24,7 @@ class NewMedicalHistoryViewModel( features.isEnabled(Feature.LabBasedStatinNudge), showSmokelessTobaccoQuestion = country.isoCountryCode != Country.ETHIOPIA, isScreeningFeatureEnabled = features.isEnabled(Feature.Screening), + showBMIContainer = features.isEnabled(Feature.ShowBMIContainer) ), init = NewMedicalHistoryInit(), loopFactoryProvider = { viewEffectsConsumer -> diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/ui/BMIContainer.kt b/app/src/main/java/org/simple/clinic/medicalhistory/ui/BMIContainer.kt new file mode 100644 index 00000000000..46ddfb728b5 --- /dev/null +++ b/app/src/main/java/org/simple/clinic/medicalhistory/ui/BMIContainer.kt @@ -0,0 +1,121 @@ +package org.simple.clinic.medicalhistory.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import org.simple.clinic.R +import org.simple.clinic.common.ui.components.ButtonSize +import org.simple.clinic.common.ui.components.TextButton +import org.simple.clinic.common.ui.theme.SimpleTheme +import org.simple.clinic.patientattribute.BMIReading + +@Composable +fun BMIContainer( + modifier: Modifier = Modifier, + bmiReading: BMIReading? = null, + onAddOrClick: () -> Unit, +) { + val isBmiRecorded = bmiReading?.calculateBMI() != null + Card(modifier = modifier) { + Column( + modifier = Modifier.padding( + start = dimensionResource(R.dimen.spacing_16), + bottom = dimensionResource(R.dimen.spacing_12) + ) + ) { + + Row( + modifier = Modifier.padding( + top = dimensionResource(R.dimen.spacing_4), + end = dimensionResource(R.dimen.spacing_8) + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing_8)) + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.medicalhistorysummaryview_bmi), + style = SimpleTheme.typography.subtitle1Medium, + color = MaterialTheme.colors.onSurface, + ) + + TextButton( + buttonSize = ButtonSize.ExtraSmall, + onClick = onAddOrClick + ) { + val btnText = if (isBmiRecorded) R.string.bmi_edit else R.string.bmi_add + Text( + text = stringResource(btnText).uppercase(), + style = MaterialTheme.typography.button, + ) + } + } + + val bmiText = buildAnnotatedString { + if (isBmiRecorded) { + append(bmiReading.calculateBMI().toString()) + + append(" ") + + withStyle( + SpanStyle( + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) + ) + ) { + append("(${bmiReading.weight.toInt()}kg, ${bmiReading.height.toInt()}cm)") + } + } else { + + withStyle( + SpanStyle( + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) + ) + ) { + append(stringResource(R.string.bmi_none)) + } + } + } + Text( + modifier = Modifier + .offset(y = (-dimensionResource(R.dimen.spacing_4))) + .padding(end = dimensionResource(R.dimen.spacing_16)), + text = bmiText, + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + ) + } + } +} + +@Preview +@Composable +private fun BMINoneContainerPreview() { + SimpleTheme { + BMIContainer( + ) { } + } +} + +@Preview +@Composable +private fun BMIContainerPreview() { + SimpleTheme { + BMIContainer( + bmiReading = BMIReading(height = 150f, weight = 50f) + ) { } + } +} diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/ui/NewMedicalHistoryUi.kt b/app/src/main/java/org/simple/clinic/medicalhistory/ui/NewMedicalHistoryUi.kt index 1719e520d50..d6d41519d90 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/ui/NewMedicalHistoryUi.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/ui/NewMedicalHistoryUi.kt @@ -39,6 +39,7 @@ fun NewMedicalHistoryUi( model: NewMedicalHistoryModel, navigationIconClick: () -> Unit, onNextClick: () -> Unit, + onAddBMIClick: () -> Unit, onSelectionChange: (MedicalHistoryQuestion, Answer) -> Unit, ) { SimpleTheme { @@ -125,6 +126,12 @@ fun NewMedicalHistoryUi( onAnswerChange = onSelectionChange ) } + if (model.showBMIContainer) { + BMIContainer( + bmiReading = model.bmiReading, + onAddOrClick = onAddBMIClick + ) + } } } } @@ -139,12 +146,14 @@ private val previewMedicalHistoryModel = NewMedicalHistoryModel( ), ongoingPatientEntry = null, ongoingMedicalHistoryEntry = OngoingMedicalHistoryEntry(), + bmiReading = null, currentFacility = null, nextButtonState = null, hasShownChangeDiagnosisError = true, showIsSmokingQuestion = true, showSmokelessTobaccoQuestion = true, - isScreeningFeatureEnabled = true + isScreeningFeatureEnabled = true, + showBMIContainer = true, ) @Preview @@ -153,7 +162,8 @@ private fun NewMedicalHistoryUiPreview() { NewMedicalHistoryUi( model = previewMedicalHistoryModel, navigationIconClick = {}, - onNextClick = {} + onNextClick = {}, + onAddBMIClick = {} ) { _, _ -> //do nothing } diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffect.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffect.kt index 44179b2538f..53d5b52e188 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffect.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffect.kt @@ -1,18 +1,14 @@ package org.simple.clinic.patientattribute.entry import org.simple.clinic.patientattribute.BMIReading -import java.util.UUID sealed class BMIEntryEffect -data class CreateNewBMIEntry( - val patientUUID: UUID, - val reading: BMIReading -) : BMIEntryEffect() - sealed class BMIEntryViewEffect : BMIEntryEffect() -data object CloseSheet : BMIEntryViewEffect() +data class CloseSheet( + val bmiReading: BMIReading? = null +) : BMIEntryViewEffect() data object ChangeFocusToHeight : BMIEntryViewEffect() diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandler.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandler.kt index 5aa7aeaa8a5..bf875ce18f6 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandler.kt @@ -1,22 +1,14 @@ package org.simple.clinic.patientattribute.entry -import com.spotify.mobius.functions.Consumer import com.spotify.mobius.rx2.RxMobius -import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.reactivex.ObservableTransformer -import org.simple.clinic.patientattribute.PatientAttributeRepository -import org.simple.clinic.user.User import org.simple.clinic.util.scheduler.SchedulersProvider -import org.simple.clinic.uuid.UuidGenerator class BMIEntryEffectHandler @AssistedInject constructor( @Assisted private val ui: BMIEntryUi, - private val patientAttributeRepository: PatientAttributeRepository, - private val currentUser: Lazy, - private val uuidGenerator: UuidGenerator, private val schedulersProvider: SchedulersProvider, ) { @@ -28,26 +20,13 @@ class BMIEntryEffectHandler @AssistedInject constructor( fun build(): ObservableTransformer { return RxMobius .subtypeEffectHandler() - .addTransformer(CreateNewBMIEntry::class.java, createNewBMIEntry()) - .addAction(CloseSheet::class.java, ui::closeSheet, schedulersProvider.ui()) + .addConsumer( + CloseSheet::class.java, + { effect -> ui.closeSheet(effect.bmiReading) }, + schedulersProvider.ui() + ) .addAction(ChangeFocusToHeight::class.java, ui::changeFocusToHeight, schedulersProvider.ui()) .addAction(ChangeFocusToWeight::class.java, ui::changeFocusToWeight, schedulersProvider.ui()) .build() } - - private fun createNewBMIEntry(): ObservableTransformer { - return ObservableTransformer { createNewBMIEntries -> - createNewBMIEntries - .observeOn(schedulersProvider.io()) - .map { createNewBMIEntry -> - patientAttributeRepository.save( - bmiReading = createNewBMIEntry.reading, - patientUuid = createNewBMIEntry.patientUUID, - loggedInUserUuid = currentUser.get().uuid, - uuid = uuidGenerator.v4(), - ) - BMISaved - } - } - } } diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEvent.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEvent.kt index 318357d5d24..906fddfb1ee 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEvent.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryEvent.kt @@ -18,4 +18,3 @@ data object BackPressed : BMIEntryEvent() { override val analyticsName = "BMI Entry::Back Pressed" } -data object BMISaved : BMIEntryEvent() diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryModel.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryModel.kt index 55fbe7a0af0..8923314febc 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryModel.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryModel.kt @@ -2,17 +2,17 @@ package org.simple.clinic.patientattribute.entry import android.os.Parcelable import kotlinx.parcelize.Parcelize -import java.util.UUID +import org.simple.clinic.patientattribute.BMIReading @Parcelize data class BMIEntryModel( - val patientUUID: UUID, - val height: String = "", - val weight: String = "" + val height: String, + val weight: String, ) : Parcelable { companion object { - fun default(patientUUID: UUID) = BMIEntryModel( - patientUUID = patientUUID, + fun default(bmiReading: BMIReading?) = BMIEntryModel( + height = bmiReading?.height?.toInt()?.toString().orEmpty(), + weight = bmiReading?.weight?.toInt()?.toString().orEmpty(), ) } diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntrySheet.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntrySheet.kt index 9842ca8e1f3..e928fe6b0ee 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntrySheet.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntrySheet.kt @@ -23,8 +23,8 @@ import org.simple.clinic.navigation.v2.Router import org.simple.clinic.navigation.v2.ScreenKey import org.simple.clinic.navigation.v2.Succeeded import org.simple.clinic.navigation.v2.fragments.BaseBottomSheet +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.widgets.UiEvent -import java.util.UUID import javax.inject.Inject class BMIEntrySheet : BaseBottomSheet< @@ -36,142 +36,162 @@ class BMIEntrySheet : BaseBottomSheet< BMIEntryViewEffect>(), BMIEntryUi { - @Inject - lateinit var effectHandlerFactory: BMIEntryEffectHandler.Factory - - @Inject - lateinit var router: Router - - private val additionalEvents = DeferredEventSource() - - private val rootLayout - get() = binding.rootLayout - - private val backImageButton - get() = binding.backImageButton - - private val heightEditText - get() = binding.heightEditText - - private val weightEditText - get() = binding.weightEditText - - private val bmiTextView - get() = binding.bmiTextView - - override fun defaultModel() = BMIEntryModel.default( - patientUUID = screenKey.patientId, - ) - - override fun uiRenderer() = BMIEntryUiRenderer(this) - - override fun bindView(inflater: LayoutInflater, container: ViewGroup?) = - SheetBmiReadingBinding.inflate(layoutInflater, container, false) - - override fun createUpdate() = BMIEntryUpdate() - - override fun createEffectHandler(viewEffectsConsumer: Consumer) = - effectHandlerFactory.create(this).build() - - override fun additionalEventSources() = listOf( - additionalEvents - ) - - override fun events() = Observable - .mergeArray( - heightChanges(), - weightChanges(), - weightBackspaceClicks(), - imeDoneClicks(), - hardwareBackPresses(), - backButtonClicks(), - ) - .compose(ReportAnalyticsEvents()) - .cast() - - override fun onAttach(context: Context) { - super.onAttach(context) - context.injector().inject(this) - } - - private fun backButtonClicks(): Observable { - return backImageButton - .clicks() - .map { BackPressed } - } - - private fun heightChanges() = heightEditText - .textChanges() - .map(CharSequence::toString) - .map(::HeightChanged) - - private fun weightChanges() = weightEditText - .textChanges() - .map(CharSequence::toString) - .map(::WeightChanged) - - private fun weightBackspaceClicks(): Observable { - return weightEditText - .backspaceClicks - .map { WeightBackspaceClicked } + @Inject + lateinit var effectHandlerFactory: BMIEntryEffectHandler.Factory + + @Inject + lateinit var router: Router + + private val additionalEvents = DeferredEventSource() + + private val rootLayout + get() = binding.rootLayout + + private val backImageButton + get() = binding.backImageButton + + private val heightEditText + get() = binding.heightEditText + + private val weightEditText + get() = binding.weightEditText + + private val bmiTextView + get() = binding.bmiTextView + + override fun defaultModel() = BMIEntryModel.default( + bmiReading = screenKey.bmiReading, + ) + + override fun uiRenderer() = BMIEntryUiRenderer(this) + + override fun bindView(inflater: LayoutInflater, container: ViewGroup?) = + SheetBmiReadingBinding.inflate(layoutInflater, container, false) + + override fun createUpdate() = BMIEntryUpdate() + + override fun createEffectHandler(viewEffectsConsumer: Consumer) = + effectHandlerFactory.create(this).build() + + override fun additionalEventSources() = listOf( + additionalEvents + ) + + override fun events() = Observable + .mergeArray( + heightChanges(), + weightChanges(), + weightBackspaceClicks(), + imeDoneClicks(), + hardwareBackPresses(), + backButtonClicks(), + ) + .compose(ReportAnalyticsEvents()) + .cast() + + override fun onAttach(context: Context) { + super.onAttach(context) + context.injector().inject(this) + } + + private fun backButtonClicks(): Observable { + return backImageButton + .clicks() + .map { BackPressed } + } + + private fun heightChanges() = heightEditText + .textChanges() + .skipInitialValue() + .map(CharSequence::toString) + .map(::HeightChanged) + + private fun weightChanges() = weightEditText + .textChanges() + .skipInitialValue() + .map(CharSequence::toString) + .map(::WeightChanged) + + private fun weightBackspaceClicks(): Observable { + return weightEditText + .backspaceClicks + .map { WeightBackspaceClicked } + } + + private fun imeDoneClicks(): Observable { + return listOf(heightEditText, weightEditText) + .map { it.editorActions { actionId -> actionId == EditorInfo.IME_ACTION_DONE } } + .toObservable() + .flatMap { it } + .map { SaveClicked } + } + + private fun hardwareBackPresses(): Observable { + return Observable.create { emitter -> + val interceptor = { + emitter.onNext(BackPressed) + } + emitter.setCancellable { rootLayout.backKeyPressInterceptor = null } + rootLayout.backKeyPressInterceptor = interceptor } + } - private fun imeDoneClicks(): Observable { - return listOf(heightEditText, weightEditText) - .map { it.editorActions { actionId -> actionId == EditorInfo.IME_ACTION_DONE } } - .toObservable() - .flatMap { it } - .map { SaveClicked } + override fun updateHeight(height: String) { + if (heightEditText.text.toString() != height) { + heightEditText.setText(height) } + } - private fun hardwareBackPresses(): Observable { - return Observable.create { emitter -> - val interceptor = { - emitter.onNext(BackPressed) - } - emitter.setCancellable { rootLayout.backKeyPressInterceptor = null } - rootLayout.backKeyPressInterceptor = interceptor - } + override fun updateWeight(weight: String) { + if (weightEditText.text.toString() != weight) { + weightEditText.setText(weight) } + } - override fun closeSheet() { - router.popWithResult(Succeeded(BMIAdded)) - } + override fun closeSheet(bmiReading: BMIReading?) { + router.popWithResult( + Succeeded(BMIAdded(bmiReading)) + ) + } - override fun changeFocusToHeight() { - heightEditText.requestFocus() - } + override fun changeFocusToHeight() { + heightEditText.requestFocus() + heightEditText.setSelection(heightEditText.text?.length ?: 0) + } - override fun changeFocusToWeight() { - weightEditText.requestFocus() - } + override fun changeFocusToWeight() { + weightEditText.requestFocus() + weightEditText.setSelection(weightEditText.text?.length ?: 0) + } - override fun showBMI(bmi: String) { - bmiTextView.text = getString(R.string.bmi_x, bmi) - bmiTextView.visibility = View.VISIBLE - } + override fun showBMI(bmi: String) { + bmiTextView.text = getString(R.string.bmi_x, bmi) + bmiTextView.visibility = View.VISIBLE + } - override fun hideBMI() { - bmiTextView.visibility = View.GONE - } + override fun hideBMI() { + bmiTextView.visibility = View.GONE + } - @Parcelize - data class Key( - val patientId: UUID, - ) : ScreenKey() { + @Parcelize + data class Key( + val bmiReading: BMIReading? = null, + ) : ScreenKey() { - override val analyticsName = "BMI Entry Sheet" + override val analyticsName = "BMI Entry Sheet" - override val type = ScreenType.Modal + override val type = ScreenType.Modal - override fun instantiateFragment() = BMIEntrySheet() - } + override fun instantiateFragment() = BMIEntrySheet() + } - interface Injector { - fun inject(target: BMIEntrySheet) - } + interface Injector { + fun inject(target: BMIEntrySheet) + } - @Parcelize - object BMIAdded : Parcelable + @Parcelize + data class BMIAdded( + val bmiReading: BMIReading? + ) : Parcelable } diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUi.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUi.kt index 5214ca5a17e..6eeec310d5f 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUi.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUi.kt @@ -1,7 +1,11 @@ package org.simple.clinic.patientattribute.entry +import org.simple.clinic.patientattribute.BMIReading + interface BMIEntryUi { - fun closeSheet() + fun updateHeight(height: String) + fun updateWeight(weight: String) + fun closeSheet(bmiReading: BMIReading?) fun changeFocusToHeight() fun changeFocusToWeight() fun showBMI(bmi: String) diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRenderer.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRenderer.kt index b3ae5408802..262b9c4ce05 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRenderer.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRenderer.kt @@ -8,6 +8,9 @@ class BMIEntryUiRenderer( ) : ViewRenderer { override fun render(model: BMIEntryModel) { + ui.updateHeight(model.height) + ui.updateWeight(model.weight) + if (model.height.isNotEmpty() && model.weight.isNotEmpty()) { val bmi = BMIReading( height = model.height.toFloat(), diff --git a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdate.kt b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdate.kt index af8d3bb9736..baae6376bb5 100644 --- a/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdate.kt +++ b/app/src/main/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdate.kt @@ -15,9 +15,8 @@ class BMIEntryUpdate : Update { is HeightChanged -> onHeightChanged(model, event) is WeightChanged -> next(model.weightChanged(event.weight)) is WeightBackspaceClicked -> onWeightBackSpaceClicked(model) - is SaveClicked -> dispatch(CreateNewBMIEntry(model.patientUUID, BMIReading(height = model.height.toFloat(), weight = model.weight.toFloat()))) - is BackPressed -> dispatch(CloseSheet) - is BMISaved -> dispatch(CloseSheet) + is SaveClicked -> dispatch(CloseSheet(BMIReading(height = model.height.toFloat(), weight = model.weight.toFloat()))) + is BackPressed -> dispatch(CloseSheet()) } } diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt index 15784a3b9dd..82213ec45e2 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt @@ -6,6 +6,7 @@ import org.simple.clinic.facility.Facility import org.simple.clinic.patient.Answer import org.simple.clinic.patient.Patient import org.simple.clinic.patient.businessid.Identifier +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.reassignpatient.ReassignPatientSheetOpenedFrom import java.time.Instant import java.util.UUID @@ -33,6 +34,10 @@ data class LoadDataForDoneClick( val canShowPatientReassignmentWarning: Boolean ) : PatientSummaryEffect() +data class LoadBMiReading( + val patientUuid: UUID, +) : PatientSummaryEffect() + data class TriggerSync(val sheetOpenedFrom: AppointmentSheetOpenedFrom) : PatientSummaryEffect() data class FetchHasShownMissingPhoneReminder(val patientUuid: UUID) : PatientSummaryEffect() @@ -61,6 +66,11 @@ data class MarkHypertensionDiagnosis(val patientUuid: UUID) : PatientSummaryEffe data class LoadStatinPrescriptionCheckInfo(val patient: Patient) : PatientSummaryEffect() +data class CreateNewBMIEntry( + val patientUUID: UUID, + val reading: BMIReading +) : PatientSummaryEffect() + data class CalculateNonLabBasedCVDRisk(val patient: Patient) : PatientSummaryEffect() data class CalculateLabBasedCVDRisk(val patient: Patient) : PatientSummaryEffect() @@ -150,6 +160,6 @@ data class ShowHypertensionDiagnosisWarning(val continueToDiabetesDiagnosisWarni data object ShowTobaccoStatusDialog : PatientSummaryViewEffect() -data class OpenBMIEntrySheet(val patientUuid: UUID) : PatientSummaryViewEffect() +data class OpenBMIEntrySheet(val bmiReading: BMIReading?) : PatientSummaryViewEffect() data class OpenCholesterolEntrySheet(val patientUuid: UUID) : PatientSummaryViewEffect() diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt index c247885c384..49b69e7d988 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt @@ -96,6 +96,7 @@ class PatientSummaryEffectHandler @AssistedInject constructor( .addTransformer(MarkReminderAsShown::class.java, markReminderAsShown(schedulersProvider.io())) .addTransformer(LoadDataForBackClick::class.java, loadDataForBackClick(schedulersProvider.io())) .addTransformer(LoadDataForDoneClick::class.java, loadDataForDoneClick(schedulersProvider.io())) + .addTransformer(LoadBMiReading::class.java, loadBmiReading()) .addTransformer(TriggerSync::class.java, triggerSync()) .addTransformer(FetchHasShownMissingPhoneReminder::class.java, fetchHasShownMissingPhoneReminder(schedulersProvider.io())) .addTransformer(LoadMedicalOfficers::class.java, loadMedicalOfficers()) @@ -115,6 +116,7 @@ class PatientSummaryEffectHandler @AssistedInject constructor( .addTransformer(SaveCVDRisk::class.java, saveCVDRisk()) .addTransformer(LoadStatinInfo::class.java, loadStatinInfo()) .addConsumer(UpdateTobaccoUse::class.java, { updateTobaccoUse(it.patientId, it.isSmoker, it.isUsingSmokelessTobacco) }, schedulersProvider.io()) + .addTransformer(CreateNewBMIEntry::class.java, createNewBMIEntry()) .build() } @@ -662,4 +664,34 @@ class PatientSummaryEffectHandler @AssistedInject constructor( .map(::FetchedHasShownMissingPhoneReminder) } } + + private fun loadBmiReading(): ObservableTransformer { + return ObservableTransformer { effects -> + effects + .observeOn(schedulersProvider.io()) + .map { + val patientAttribute = patientAttributeRepository.getPatientAttributeImmediate( + patientUuid = it.patientUuid + ) + BMIReadingLoaded(patientAttribute?.bmiReading) + } + } + } + + private fun createNewBMIEntry(): ObservableTransformer { + return ObservableTransformer { createNewBMIEntries -> + createNewBMIEntries + .observeOn(schedulersProvider.io()) + .map { createNewBMIEntry -> + patientAttributeRepository.save( + bmiReading = createNewBMIEntry.reading, + patientUuid = createNewBMIEntry.patientUUID, + loggedInUserUuid = currentUser.get().uuid, + uuid = uuidGenerator.v4(), + ) + BMISaved + } + } + } + } diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt index adc9c29a9e5..dce1b9f0421 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt @@ -179,10 +179,19 @@ data class TobaccoUseAnswered( val isUsingSmokelessTobacco: Answer = Answer.Unanswered ) : PatientSummaryEvent() -data object BMIReadingAdded : PatientSummaryEvent() +data class BMIReadingLoaded( + val bmiReading: BMIReading? +) : PatientSummaryEvent() + +data class BMIReadingAdded( + val bmiReading: BMIReading +) : PatientSummaryEvent() data object AddBMIClicked : PatientSummaryEvent() data object AddCholesterolClicked: PatientSummaryEvent() data object CholesterolAdded : PatientSummaryEvent() + +data object BMISaved : PatientSummaryEvent() + diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt index 86e2a635bde..a2367962ac4 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt @@ -56,6 +56,7 @@ import org.simple.clinic.navigation.v2.Router import org.simple.clinic.navigation.v2.Succeeded import org.simple.clinic.navigation.v2.fragments.BaseScreen import org.simple.clinic.patient.businessid.Identifier +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.patientattribute.entry.BMIEntrySheet import org.simple.clinic.reassignpatient.ReassignPatientSheet import org.simple.clinic.reassignpatient.ReassignPatientSheetOpenedFrom @@ -358,7 +359,13 @@ class PatientSummaryScreen : } is ScreenRequest.BMIEntrySheet -> { - additionalEvents.notify(BMIReadingAdded) + val bmiReading = (result.result as BMIEntrySheet.BMIAdded).bmiReading + + if (bmiReading != null) { + additionalEvents.notify(BMIReadingAdded( + bmiReading = bmiReading + )) + } } is ScreenRequest.CholesterolEntrySheet -> { @@ -772,8 +779,8 @@ class PatientSummaryScreen : router.pushExpectingResult(ScreenRequest.CholesterolEntrySheet, CholesterolEntrySheet.Key(patientUuid)) } - override fun openBMIEntrySheet(patientUuid: UUID) { - router.pushExpectingResult(ScreenRequest.BMIEntrySheet, BMIEntrySheet.Key(patientUuid)) + override fun openBMIEntrySheet(bmiReading: BMIReading?) { + router.pushExpectingResult(ScreenRequest.BMIEntrySheet, BMIEntrySheet.Key(bmiReading)) } override fun openSelectFacilitySheet() { diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUiActions.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUiActions.kt index bd9f7823934..014655ec980 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUiActions.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUiActions.kt @@ -2,6 +2,7 @@ package org.simple.clinic.summary import org.simple.clinic.facility.Facility import org.simple.clinic.patient.businessid.Identifier +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.reassignpatient.ReassignPatientSheetOpenedFrom import java.util.UUID @@ -44,6 +45,6 @@ interface PatientSummaryUiActions { fun showDiabetesDiagnosisWarning() fun showHypertensionDiagnosisWarning(continueToDiabetesDiagnosisWarning: Boolean) fun showTobaccoStatusDialog() - fun openBMIEntrySheet(patientUuid: UUID) + fun openBMIEntrySheet(bmiReading: BMIReading?) fun openCholesterolEntrySheet(patientUuid: UUID) } diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt index 94adb2cc344..bce0f936a67 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt @@ -109,10 +109,12 @@ class PatientSummaryUpdate( is StatinInfoLoaded -> statinInfoLoaded(event, model) is AddTobaccoUseClicked -> dispatch(ShowTobaccoStatusDialog) is TobaccoUseAnswered -> dispatch(UpdateTobaccoUse(model.patientUuid, event.isSmoker, event.isUsingSmokelessTobacco)) - is BMIReadingAdded -> dispatch(CalculateNonLabBasedCVDRisk(model.patientSummaryProfile!!.patient)) - is AddBMIClicked -> dispatch(OpenBMIEntrySheet(model.patientUuid)) + is BMIReadingAdded -> dispatch(CreateNewBMIEntry(model.patientUuid, event.bmiReading)) + is AddBMIClicked -> dispatch(LoadBMiReading(model.patientUuid)) is AddCholesterolClicked -> dispatch(OpenCholesterolEntrySheet(model.patientUuid)) CholesterolAdded -> dispatch(CalculateLabBasedCVDRisk(model.patientSummaryProfile!!.patient)) + is BMIReadingLoaded -> dispatch(OpenBMIEntrySheet(event.bmiReading)) + is BMISaved -> dispatch(CalculateNonLabBasedCVDRisk(model.patientSummaryProfile!!.patient)) } } diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryViewEffectHandler.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryViewEffectHandler.kt index 87b7c0fc182..8b68a9d253b 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryViewEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryViewEffectHandler.kt @@ -42,7 +42,7 @@ class PatientSummaryViewEffectHandler( ShowDiabetesDiagnosisWarning -> uiActions.showDiabetesDiagnosisWarning() is ShowHypertensionDiagnosisWarning -> uiActions.showHypertensionDiagnosisWarning(viewEffect.continueToDiabetesDiagnosisWarning) is ShowTobaccoStatusDialog -> uiActions.showTobaccoStatusDialog() - is OpenBMIEntrySheet -> uiActions.openBMIEntrySheet(viewEffect.patientUuid) + is OpenBMIEntrySheet -> uiActions.openBMIEntrySheet(viewEffect.bmiReading) is OpenCholesterolEntrySheet -> uiActions.openCholesterolEntrySheet(viewEffect.patientUuid) }.exhaustive() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb1e87ffe87..c79054efa96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -578,6 +578,7 @@ Patient has hypertension? Patient has diabetes? Tobacco use + BMI No medicines added @@ -1118,6 +1119,9 @@ The Simple app contains private health information of patients (“Data”).\n\n Height (cm) Weight (kg) BMI: %s + Add + Edit + None Tobacco use? diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandlerTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandlerTest.kt index 4d03a0f5e8f..68f06e5a3ff 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandlerTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryEffectHandlerTest.kt @@ -1,6 +1,5 @@ package org.simple.clinic.medicalhistory.newentry -import dagger.Lazy import org.junit.After import org.junit.Test import org.mockito.kotlin.mock @@ -9,6 +8,7 @@ import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.simple.clinic.TestData import org.simple.clinic.mobius.EffectHandlerTestCase +import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.sync.DataSync import org.simple.clinic.util.scheduler.TrampolineSchedulersProvider import org.simple.clinic.uuid.FakeUuidGenerator @@ -31,9 +31,10 @@ class NewMedicalHistoryEffectHandlerTest { schedulersProvider = TrampolineSchedulersProvider(), patientRepository = mock(), medicalHistoryRepository = mock(), + patientAttributeRepository = mock(), dataSync = dataSync, - currentUser = Lazy { user }, - currentFacility = Lazy { facility }, + currentUser = { user }, + currentFacility = { facility }, uuidGenerator = FakeUuidGenerator.fixed(medicalHistoryUuid), dateOfBirthFormatter = dateOfBirthFormatter, viewEffectsConsumer = viewEffectHandler::handle @@ -164,4 +165,17 @@ class NewMedicalHistoryEffectHandlerTest { testCase.assertNoOutgoingEvents() } + + @Test + fun `when open bottom sheet effect is received, then open bottom sheet`() { + // when + val bmiReading = BMIReading(height = 175f, weight = 67f) + testCase.dispatch(OpenBMIEntrySheet(bmiReading)) + + // then + verify(uiActions).openBMIEntrySheet(bmiReading) + verifyNoMoreInteractions(uiActions) + + testCase.assertNoOutgoingEvents() + } } diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt index f5175646c32..e5fc92b3615 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt @@ -16,6 +16,7 @@ class NewMedicalHistoryInitTest { showIsSmokingQuestion = false, showSmokelessTobaccoQuestion = false, isScreeningFeatureEnabled = true, + showBMIContainer = true ) private val initSpec = InitSpec(NewMedicalHistoryInit()) diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt index dda250f0c55..fada15c5128 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt @@ -30,6 +30,7 @@ import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry import org.simple.clinic.patient.OngoingNewPatientEntry import org.simple.clinic.patient.PatientProfile import org.simple.clinic.patient.PatientRepository +import org.simple.clinic.patientattribute.PatientAttributeRepository import org.simple.clinic.util.RxErrorsRule import org.simple.clinic.util.scheduler.TrampolineSchedulersProvider import org.simple.clinic.uuid.FakeUuidGenerator @@ -47,6 +48,8 @@ class NewMedicalHistoryScreenLogicTest { private val uiActions: NewMedicalHistoryUiActions = mock() private val medicalHistoryRepository: MedicalHistoryRepository = mock() + + private val patientAttributeRepository: PatientAttributeRepository = mock() private val patientRepository: PatientRepository = mock() private val uiEvents = PublishSubject.create() @@ -291,6 +294,7 @@ class NewMedicalHistoryScreenLogicTest { schedulersProvider = TrampolineSchedulersProvider(), patientRepository = patientRepository, medicalHistoryRepository = medicalHistoryRepository, + patientAttributeRepository = patientAttributeRepository, dataSync = mock(), currentUser = { user }, currentFacility = { facility }, @@ -308,6 +312,7 @@ class NewMedicalHistoryScreenLogicTest { showIsSmokingQuestion = true, showSmokelessTobaccoQuestion = true, isScreeningFeatureEnabled = true, + showBMIContainer = true, ), init = NewMedicalHistoryInit(), update = NewMedicalHistoryUpdate(), diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt index e36e4478a0e..0654341b898 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt @@ -31,6 +31,7 @@ class NewMedicalHistoryUpdateTest { showIsSmokingQuestion = false, showSmokelessTobaccoQuestion = false, isScreeningFeatureEnabled = false, + showBMIContainer = false, ) private val defaultScreeningModel = NewMedicalHistoryModel.default( @@ -38,6 +39,7 @@ class NewMedicalHistoryUpdateTest { showIsSmokingQuestion = false, showSmokelessTobaccoQuestion = false, isScreeningFeatureEnabled = true, + showBMIContainer = true, ) private val facilityWithDiabetesManagementEnabled = TestData.facility( uuid = UUID.fromString("3c7bc1c8-1bb6-4c3a-b6d0-52700bdaac5c"), @@ -243,7 +245,7 @@ class NewMedicalHistoryUpdateTest { .then( assertThatNext( hasModel(model.registeringPatient()), - hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry) as NewMedicalHistoryEffect) + hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry, model.bmiReading) as NewMedicalHistoryEffect) ) ) } @@ -317,7 +319,8 @@ class NewMedicalHistoryUpdateTest { country = bangladesh, showIsSmokingQuestion = false, showSmokelessTobaccoQuestion = false, - isScreeningFeatureEnabled = true + isScreeningFeatureEnabled = true, + showBMIContainer = true, ) .ongoingPatientEntryLoaded(patientEntry) .currentFacilityLoaded(facilityWithDiabetesManagementEnabled) @@ -331,7 +334,7 @@ class NewMedicalHistoryUpdateTest { .then( assertThatNext( hasModel(model.registeringPatient()), - hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry)) + hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry, model.bmiReading)) ) ) } @@ -370,7 +373,7 @@ class NewMedicalHistoryUpdateTest { .then( assertThatNext( hasModel(model.registeringPatient()), - hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry)) + hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry, model.bmiReading)) ) ) } @@ -389,7 +392,7 @@ class NewMedicalHistoryUpdateTest { .then( assertThatNext( hasModel(model.registeringPatient()), - hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry)) + hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry, model.bmiReading)) ) ) } @@ -428,7 +431,7 @@ class NewMedicalHistoryUpdateTest { .whenEvent(SaveMedicalHistoryClicked()) .then(assertThatNext( hasModel(model.registeringPatient()), - hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry)) + hasEffects(RegisterPatient(model.ongoingMedicalHistoryEntry, model.bmiReading)) )) } @@ -442,4 +445,15 @@ class NewMedicalHistoryUpdateTest { hasEffects(GoBack) )) } + + @Test + fun `when add bmi is clicked, then open bmi entry sheet`() { + updateSpec + .given(defaultModel) + .whenEvent(AddOrEditBMIClicked) + .then(assertThatNext( + hasNoModel(), + hasEffects(OpenBMIEntrySheet(defaultModel.bmiReading)) + )) + } } diff --git a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandlerTest.kt b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandlerTest.kt index 46c1f1ce6e3..be6662c1294 100644 --- a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandlerTest.kt +++ b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryEffectHandlerTest.kt @@ -7,25 +7,12 @@ import org.mockito.kotlin.verify import org.simple.clinic.mobius.EffectHandlerTestCase import org.simple.clinic.patientattribute.BMIReading import org.simple.clinic.util.scheduler.TestSchedulersProvider -import org.simple.clinic.TestData -import java.util.UUID class BMIEntryEffectHandlerTest { private val ui = mock() - private val facility = TestData.facility(uuid = UUID.fromString("7a7df523-8397-4c9e-bcef-d0047ea2e969")) - private val user = TestData.loggedInUser( - uuid = UUID.fromString("5f8c9705-6732-4d1c-aea3-3b5ab10d4a0e"), - registrationFacilityUuid = facility.uuid, - currentFacilityUuid = facility.uuid - ) - - private val effectHandler = BMIEntryEffectHandler( ui = ui, - patientAttributeRepository = mock(), - currentUser = { user }, - uuidGenerator = org.mockito.kotlin.mock(), schedulersProvider = TestSchedulersProvider.trampoline(), ).build() @@ -36,26 +23,16 @@ class BMIEntryEffectHandlerTest { testCase.dispose() } - @Test - fun `when create bmi entry is received, then bmi should be saved`() { - //when - testCase.dispatch(CreateNewBMIEntry( - reading = BMIReading(height = 177f, weight = 63f), - patientUUID = UUID.randomUUID() - )) - - //then - testCase.assertOutgoingEvents(BMISaved) - } - @Test fun `when close sheet view effect is received, then close sheet`() { + //given + val bmiReading = BMIReading(height = 177f, weight = 63f) //when - testCase.dispatch(CloseSheet) + testCase.dispatch(CloseSheet(bmiReading)) //then testCase.assertNoOutgoingEvents() - verify(ui).closeSheet() + verify(ui).closeSheet(bmiReading) } @Test diff --git a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRendererTest.kt b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRendererTest.kt index 0c5a52d5514..9d400ec0935 100644 --- a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRendererTest.kt +++ b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUiRendererTest.kt @@ -5,37 +5,45 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.simple.clinic.patientattribute.BMIReading -import java.util.UUID class BMIEntryUiRendererTest { private val ui = mock() private val uiRenderer = BMIEntryUiRenderer(ui) - private val patientUuid = UUID.fromString("d6dd1708-9061-4c6c-b5c7-47b132198862") - private val defaultModel = BMIEntryModel - .default(patientUuid) @Test - fun `when the sheet is show for a new entry, then hide bmi`() { + fun `when the sheet is shown for a new entry, then hide bmi`() { + // given + val model = BMIEntryModel.default(null) + // when - uiRenderer.render(defaultModel) + uiRenderer.render(model) // then + verify(ui).updateHeight("") + verify(ui).updateWeight("") verify(ui).hideBMI() verifyNoMoreInteractions(ui) } @Test fun `when the height and weight are entered, then show bmi`() { - //given + // given val height = 177f val weight = 68f + val reading = BMIReading(height, weight) val bmi = reading.calculateBMI().toString() + val model = BMIEntryModel.default(null) + .heightChanged(height.toInt().toString()) + .weightChanged(weight.toInt().toString()) + // when - uiRenderer.render(defaultModel.heightChanged(height.toString()).weightChanged(weight.toString())) + uiRenderer.render(model) // then + verify(ui).updateHeight("177") + verify(ui).updateWeight("68") verify(ui).showBMI(bmi) verifyNoMoreInteractions(ui) } diff --git a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdateTest.kt b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdateTest.kt index 3c6131a564f..57b6fd4e662 100644 --- a/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/patientattribute/entry/BMIEntryUpdateTest.kt @@ -8,38 +8,24 @@ import com.spotify.mobius.test.UpdateSpec import com.spotify.mobius.test.UpdateSpec.assertThatNext import org.junit.Test import org.simple.clinic.patientattribute.BMIReading -import java.util.UUID class BMIEntryUpdateTest { - private val patientUuid = UUID.fromString("720f6fd4-4c4e-406a-b221-881990a962d4") - - private val defaultModel = BMIEntryModel.default(patientUuid) + private val defaultModel = BMIEntryModel.default(BMIReading(height = 177f, weight = 63f)) private val update = BMIEntryUpdate() private val spec = UpdateSpec(update) @Test - fun `when the save button is clicked, then save the bmi`() { + fun `when the save button is clicked, then close the sheet and return bmi reading`() { val model = defaultModel.weightChanged("63").heightChanged("177") spec .given(model) .whenEvent(SaveClicked) .then(assertThatNext( hasNoModel(), - hasEffects(CreateNewBMIEntry(model.patientUUID, BMIReading(height = model.height.toFloat(), weight = model.weight.toFloat()))) - )) - } - - @Test - fun `when bmi is saved, then close the sheet`() { - spec - .given(defaultModel) - .whenEvent(BMISaved) - .then(assertThatNext( - hasNoModel(), - hasEffects(CloseSheet) + hasEffects(CloseSheet(BMIReading(height = model.height.toFloat(), weight = model.weight.toFloat()))) )) } @@ -93,11 +79,12 @@ class BMIEntryUpdateTest { @Test fun `when weight backspace is clicked and is empty, then update the model and change the focus to height`() { + val model = defaultModel.weightChanged("") spec - .given(defaultModel) + .given(model) .whenEvent(WeightBackspaceClicked) .then(assertThatNext( - hasModel(defaultModel.deleteWeightLastDigit()), + hasModel(model), hasEffects(ChangeFocusToHeight) )) } @@ -109,7 +96,7 @@ class BMIEntryUpdateTest { .whenEvent(BackPressed) .then(assertThatNext( hasNoModel(), - hasEffects(CloseSheet) + hasEffects(CloseSheet()) )) } } diff --git a/app/src/test/java/org/simple/clinic/summary/PatientSummaryEffectHandlerTest.kt b/app/src/test/java/org/simple/clinic/summary/PatientSummaryEffectHandlerTest.kt index 318f2a3d39a..209986b7370 100644 --- a/app/src/test/java/org/simple/clinic/summary/PatientSummaryEffectHandlerTest.kt +++ b/app/src/test/java/org/simple/clinic/summary/PatientSummaryEffectHandlerTest.kt @@ -1092,12 +1092,13 @@ class PatientSummaryEffectHandlerTest { @Test fun `when open BMI entry sheet view effect is received, then open the BMI entry sheet`() { // when - testCase.dispatch(OpenBMIEntrySheet(patientUuid)) + val bmiReading = BMIReading(height = 177f, weight = 53f) + testCase.dispatch(OpenBMIEntrySheet(bmiReading)) // then testCase.assertNoOutgoingEvents() - verify(uiActions).openBMIEntrySheet(patientUuid) + verify(uiActions).openBMIEntrySheet(bmiReading) verifyNoMoreInteractions(uiActions) } @@ -1111,4 +1112,26 @@ class PatientSummaryEffectHandlerTest { verify(uiActions).openCholesterolEntrySheet(patientUuid = patientUuid) verifyNoMoreInteractions(uiActions) } + + @Test + fun `when load bmi reading effect is received, then bmi reading should be loaded`() { + // when + testCase.dispatch(LoadBMiReading(patientUuid = patientUuid)) + + // then + //then + testCase.assertOutgoingEvents(BMIReadingLoaded(null)) + } + + @Test + fun `when create new bmi entry effect is received, then save bmi`() { + // when + val bmiReading = BMIReading(height = 177f, weight = 53f) + + testCase.dispatch(CreateNewBMIEntry(patientUuid, bmiReading)) + + // then + //then + testCase.assertOutgoingEvents(BMISaved) + } } diff --git a/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt b/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt index 31e82b0ec74..b8a7bd059c0 100644 --- a/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt @@ -2692,7 +2692,7 @@ class PatientSummaryUpdateTest { } @Test - fun `when add bmi button is clicked, then open the bmi entry sheet`() { + fun `when add bmi button is clicked, then load bmi reading`() { val model = defaultModel .patientSummaryProfileLoaded(patientSummaryProfile) @@ -2700,25 +2700,80 @@ class PatientSummaryUpdateTest { .given(model) .whenEvent(AddBMIClicked) .then(assertThatNext( - hasEffects(OpenBMIEntrySheet(model.patientUuid)), + hasEffects(LoadBMiReading(model.patientUuid)), hasNoModel() )) } @Test - fun `when BMI reading is added, then calculate the cvd risk`() { + fun `when bmi reading is loaded, then open bmi entry sheet`() { val model = defaultModel .patientSummaryProfileLoaded(patientSummaryProfile) + val bmiReading = BMIReading( + height = 165f, + weight = 60f + ) + updateSpec .given(model) - .whenEvent(BMIReadingAdded) + .whenEvent(BMIReadingLoaded(bmiReading)) + .then(assertThatNext( + hasEffects(OpenBMIEntrySheet(bmiReading)), + hasNoModel() + )) + } + + @Test + fun `when BMI reading is added, then create new bmi entry`() { + val model = defaultModel + .patientSummaryProfileLoaded(patientSummaryProfile) + + val bmiReading = BMIReading( + height = 165f, + weight = 60f + ) + updateSpec + .given(model) + .whenEvent(BMIReadingAdded(bmiReading)) + .then(assertThatNext( + hasEffects(CreateNewBMIEntry(patientUuid, bmiReading)), + hasNoModel() + )) + } + + @Test + fun `when BMI reading is saved, then calculate non lab based cvd risk`() { + val model = defaultModel + .patientSummaryProfileLoaded(patientSummaryProfile) + + updateSpec + .given(model) + .whenEvent(BMISaved) .then(assertThatNext( hasEffects(CalculateNonLabBasedCVDRisk(patientSummaryProfile.patient)), hasNoModel() )) } + @Test + fun `when BMI reading is loaded, then open bmi entry sheet`() { + val model = defaultModel + .patientSummaryProfileLoaded(patientSummaryProfile) + + val bmiReading = BMIReading( + height = 165f, + weight = 60f + ) + updateSpec + .given(model) + .whenEvent(BMIReadingLoaded(bmiReading)) + .then(assertThatNext( + hasEffects(OpenBMIEntrySheet(bmiReading)), + hasNoModel() + )) + } + @Test fun `when lab based statin feature is enabled and statin prescription check info is loaded and person is below 40 without cvd, then update the state with false`() { val updateSpec = UpdateSpec(PatientSummaryUpdate(