From 3391b0db57a708ce607533281c4109daf995ddf8 Mon Sep 17 00:00:00 2001 From: Christopher Seven Phiri Date: Mon, 23 Sep 2024 08:27:51 +0200 Subject: [PATCH] Make sync after save configurable (#110) * Settings * Update QuestionnaireViewModel.kt * Update SettingsScreen.kt * Add auto sync chip switch * Update build.gradle.kts * Update SettingsScreen.kt --- .../ui/questionnaire/QuestionnaireActivity.kt | 4 -- .../questionnaire/QuestionnaireViewModel.kt | 7 +++- .../engine/ui/settings/SettingsScreen.kt | 21 +++++++++++ .../engine/util/SharedPreferenceKey.kt | 1 + android/quest/build.gradle.kts | 6 +-- .../patient/profile/PatientProfileScreen.kt | 37 ++++++++++++++++++- .../profile/PatientProfileViewModel.kt | 17 ++++++++- 7 files changed, 82 insertions(+), 11 deletions(-) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt index efadf1455e..5e2223f850 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt @@ -53,7 +53,6 @@ import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.StringType import org.smartregister.fhircore.engine.R import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry -import org.smartregister.fhircore.engine.sync.SyncBroadcaster import org.smartregister.fhircore.engine.trace.PerformanceReporter import org.smartregister.fhircore.engine.ui.base.AlertDialogue import org.smartregister.fhircore.engine.ui.base.AlertDialogue.showConfirmAlert @@ -80,8 +79,6 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList @Inject lateinit var dispatcherProvider: DefaultDispatcherProvider - @Inject lateinit var syncBroadcaster: SyncBroadcaster - @Inject lateinit var tracer: PerformanceReporter open val questionnaireViewModel: QuestionnaireViewModel by viewModels() @@ -393,7 +390,6 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList extras: List? = null, ) { dismissSaveProcessing() - syncBroadcaster.runSync() postSaveSuccessful(questionnaireResponse, extras) } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt index c8d6a159da..979343bba8 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt @@ -69,6 +69,7 @@ import org.hl7.fhir.r4.model.Task import org.hl7.fhir.r4.model.Task.TaskStatus import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.data.local.DefaultRepository +import org.smartregister.fhircore.engine.sync.SyncBroadcaster import org.smartregister.fhircore.engine.task.FhirCarePlanGenerator import org.smartregister.fhircore.engine.trace.PerformanceReporter import org.smartregister.fhircore.engine.util.AssetUtil @@ -103,9 +104,10 @@ constructor( val defaultRepository: DefaultRepository, val configurationRegistry: ConfigurationRegistry, val transformSupportServices: TransformSupportServices, - val simpleWorkerContext: SimpleWorkerContext, + private val simpleWorkerContext: SimpleWorkerContext, val dispatcherProvider: DispatcherProvider, val sharedPreferencesHelper: SharedPreferencesHelper, + var syncBroadcaster: SyncBroadcaster, var tracer: PerformanceReporter, ) : ViewModel() { @Inject lateinit var fhirCarePlanGenerator: FhirCarePlanGenerator @@ -308,6 +310,9 @@ constructor( backReference, ) extractionProgress.postValue(ExtractionProgress.Success(questionnaireResponse, extras)) + if (sharedPreferencesHelper.read(SharedPreferenceKey.SYNC_ON_SAVE.name, true)) { + syncBroadcaster.runSync() + } } catch (e: Exception) { Timber.e(e) extractionProgress.postValue(ExtractionProgress.Failed(questionnaireResponse, e)) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/settings/SettingsScreen.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/settings/SettingsScreen.kt index f4e6b3f8da..e6311cac4f 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/settings/SettingsScreen.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/settings/SettingsScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Scaffold @@ -159,10 +160,30 @@ fun SettingsScreen( values = SyncUploadStrategy.entries.map { it.name }, ) + switchPreference( + key = SharedPreferenceKey.SYNC_ON_SAVE.name, + defaultValue = true, + title = { Text(text = "Sync on form answered") }, + summary = { + Text( + text = + "When disabled, form saves will not start sync automatically, you have to manually sync the changes.", + style = MaterialTheme.typography.caption, + ) + }, + ) + switchPreference( key = SharedPreferenceKey.PATIENT_FIX_TYPE.name, defaultValue = false, title = { Text(text = "Fix patients offline") }, + summary = { + Text( + text = + "When enable the app will attempt to fix the patient completely offline which my fail", + style = MaterialTheme.typography.caption, + ) + }, ) item { diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt index 2783f1da73..a86665816d 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt @@ -32,4 +32,5 @@ enum class SharedPreferenceKey { USER_CLAIM_INFO, SYNC_UPLOAD_STRATEGY, PATIENT_FIX_TYPE, + SYNC_ON_SAVE, } diff --git a/android/quest/build.gradle.kts b/android/quest/build.gradle.kts index 30477b4538..bebbfaf056 100644 --- a/android/quest/build.gradle.kts +++ b/android/quest/build.gradle.kts @@ -140,21 +140,21 @@ android { applicationIdSuffix = ".mwcore" versionNameSuffix = "-mwcore" versionCode = 37 - versionName = "0.2.0.2-beta1" + versionName = "0.2.0.2-beta2" } create("mwcoreDev") { dimension = "apps" applicationIdSuffix = ".mwcoreDev" versionNameSuffix = "-mwcoreDev" versionCode = 37 - versionName = "0.2.0.2-beta1" + versionName = "0.2.0.2-beta2" } create("mwcoreStaging") { dimension = "apps" applicationIdSuffix = ".mwcoreStaging" versionNameSuffix = "-mwcoreStaging" versionCode = 37 - versionName = "0.2.0.2-beta1" + versionName = "0.2.0.2-beta2" } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileScreen.kt index 65184bdd17..568de03b1e 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileScreen.kt @@ -31,17 +31,20 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button +import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.Refresh import androidx.compose.runtime.Composable @@ -93,9 +96,10 @@ fun PatientProfileScreen( var showOverflowMenu by remember { mutableStateOf(false) } val viewState = patientProfileViewModel.patientProfileUiState.value val taskId by appMainViewModel.completedTaskId.collectAsState() - val syncing by remember { patientProfileViewModel.isSyncing } + val syncing by patientProfileViewModel.isSyncing.collectAsState() val tasksId = profileViewData.tasks.map { it.actionFormId } val loadingState by patientProfileViewModel.loadingState.collectAsState() + val autoSyncEnabled by patientProfileViewModel.autoSyncOn.collectAsState() val launchQuestionnaireActivityForResults = rememberLauncherForActivityResult( @@ -120,6 +124,11 @@ fun PatientProfileScreen( } }, actions = { + AutoSyncButton( + enabled = autoSyncEnabled, + isSyncing = syncing, + runSync = patientProfileViewModel::runSync, + ) IconButton(onClick = { patientProfileViewModel.reFetch() }, enabled = !syncing) { Icon( imageVector = Icons.Outlined.Refresh, @@ -339,3 +348,27 @@ fun PatientProfileScreen( } } } + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun AutoSyncButton(enabled: Boolean, isSyncing: Boolean, runSync: () -> Unit) { + if (!enabled) { + Chip( + onClick = runSync, + colors = + if (isSyncing) { + ChipDefaults.chipColors() + } else + ChipDefaults.chipColors( + backgroundColor = MaterialTheme.colors.error, + contentColor = MaterialTheme.colors.onError, + ), + ) { + if (isSyncing) { + Text(text = "Syncing...") + } else { + Text(text = "Auto sync off") + } + } + } +} diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileViewModel.kt index 82350c62a4..b10c622ccf 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/PatientProfileViewModel.kt @@ -52,6 +52,8 @@ import org.smartregister.fhircore.engine.domain.util.PaginationConstant import org.smartregister.fhircore.engine.sync.SyncBroadcaster import org.smartregister.fhircore.engine.ui.questionnaire.QuestionnaireActivity import org.smartregister.fhircore.engine.ui.questionnaire.QuestionnaireType +import org.smartregister.fhircore.engine.util.SharedPreferenceKey +import org.smartregister.fhircore.engine.util.SharedPreferencesHelper import org.smartregister.fhircore.engine.util.extension.asReference import org.smartregister.fhircore.engine.util.extension.isGuardianVisit import org.smartregister.fhircore.quest.R @@ -82,6 +84,7 @@ constructor( val configurationRegistry: ConfigurationRegistry, private val profileViewDataMapper: ProfileViewDataMapper, val registerViewDataMapper: RegisterViewDataMapper, + val sharedPreferences: SharedPreferencesHelper, ) : ViewModel() { val appFeatureName = savedStateHandle.get(NavigationArg.FEATURE) @@ -91,7 +94,7 @@ constructor( val familyId = savedStateHandle.get(NavigationArg.FAMILY_ID) // TODO: replace later with actual implementation from the engine - val isSyncing = mutableStateOf(false) + val isSyncing = MutableStateFlow(false) var patientProfileUiState: MutableState = mutableStateOf( @@ -114,6 +117,8 @@ constructor( val loadingState = MutableStateFlow>(DataLoadState.Idle) + val autoSyncOn = MutableStateFlow(true) + init { syncBroadcaster.registerSyncListener( { state -> @@ -126,6 +131,7 @@ constructor( is SyncJobStatus.Started -> { isSyncing.value = true } + is SyncJobStatus.InProgress -> {} else -> { isSyncing.value = false } @@ -135,6 +141,15 @@ constructor( ) fetchPatientProfileDataWithChildren() + checkAutoSyncStatus() + } + + private fun checkAutoSyncStatus() { + autoSyncOn.value = sharedPreferences.read(SharedPreferenceKey.SYNC_ON_SAVE.name, true) + } + + fun runSync() { + syncBroadcaster.runSync() } fun getOverflowMenuHostByPatientType(healthStatus: HealthStatus): OverflowMenuHost {