diff --git a/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkApplication.kt b/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkApplication.kt index ef072e2fd3..ae997f36ee 100644 --- a/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkApplication.kt +++ b/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkApplication.kt @@ -21,6 +21,7 @@ import android.util.Log import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration import com.google.android.fhir.datacapture.DataCaptureConfig +import com.google.firebase.analytics.ktx.analytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase import com.google.firebase.perf.ktx.performance @@ -50,6 +51,7 @@ class DataClerkApplication : Application(), DataCaptureConfig.Provider, Configur if (BuildConfig.DEBUG) { Firebase.performance.isPerformanceCollectionEnabled = false Firebase.crashlytics.setCrashlyticsCollectionEnabled(false) + Firebase.analytics.setAnalyticsCollectionEnabled(false) Timber.plant(Timber.DebugTree()) } } diff --git a/android/engine/build.gradle.kts b/android/engine/build.gradle.kts index 23f8623ffd..ee90e37d93 100644 --- a/android/engine/build.gradle.kts +++ b/android/engine/build.gradle.kts @@ -216,11 +216,12 @@ dependencies { api("com.squareup.okhttp3:okhttp:$okhttpVersion") api("com.squareup.okhttp3:logging-interceptor:$okhttpVersion") - implementation(platform("com.google.firebase:firebase-bom:32.7.3")) + implementation(platform("com.google.firebase:firebase-bom:33.0.0")) implementation("com.google.firebase:firebase-perf-ktx") implementation("com.google.firebase:firebase-crashlytics-ktx") + implementation("com.google.firebase:firebase-analytics") - implementation("androidx.core:core-splashscreen:1.0.0") + implementation("androidx.core:core-splashscreen:1.0.1") // Hilt test dependencies testImplementation("com.google.dagger:hilt-android-testing:${Deps.versions.hiltVersion}") diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt index 449d10b7f4..f6467c0470 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt @@ -36,6 +36,8 @@ data class ApplicationConfiguration( var taskOrderFilterTagViaMetaCodingSystem: String = SystemConstants.TASK_TASK_ORDER_SYSTEM, var taskFilterTagViaMetaCodingSystem: String = SystemConstants.TASK_FILTER_TAG_SYSTEM, var registrationForm: String = "patient-demographic-registration", + var supportEmail: String = "info@tingathe.org", + var supportPhoneNumber: String = "" ) : Configuration /** diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/AnalyticsModule.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/AnalyticsModule.kt index 62be255e54..07042543c3 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/AnalyticsModule.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/AnalyticsModule.kt @@ -22,6 +22,8 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton +import org.smartregister.fhircore.engine.trace.AnalyticReporter +import org.smartregister.fhircore.engine.trace.FirebaseAnalyticReporter import org.smartregister.fhircore.engine.trace.FirebasePerformanceReporter import org.smartregister.fhircore.engine.trace.PerformanceReporter @@ -35,4 +37,6 @@ class AnalyticsModule { @Provides fun providePerformanceReporter(firebasePerformance: FirebasePerformance): PerformanceReporter = FirebasePerformanceReporter(firebasePerformance) + + @Provides fun providesAnalyticsReporter(): AnalyticReporter = FirebaseAnalyticReporter() } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirResourceUtil.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirResourceUtil.kt index 82107233de..8309dd2821 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirResourceUtil.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirResourceUtil.kt @@ -209,7 +209,7 @@ constructor( appointment.status = Appointment.AppointmentStatus.WAITLIST tracingTasksToAdd.addAll(addMissedAppointment(appointment, true)) } - if(missedAppointmentInRange) { + if (missedAppointmentInRange) { appointment.status = Appointment.AppointmentStatus.NOSHOW tracingTasksToAdd.addAll(addMissedAppointment(appointment, false)) } @@ -361,13 +361,13 @@ constructor( } } } - if(!isMilestoneAppointment) { + if (!isMilestoneAppointment) { addToTracingList( - appointment, - if (isEID) { - ReasonConstants.missedRoutineAppointmentTracingCode - } else ReasonConstants.missedAppointmentTracingCode, - ) + appointment, + if (isEID) { + ReasonConstants.missedRoutineAppointmentTracingCode + } else ReasonConstants.missedAppointmentTracingCode, + ) ?.let { tracingTasks.add(it) } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/AnalyticReporter.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/AnalyticReporter.kt new file mode 100644 index 0000000000..9682646d00 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/AnalyticReporter.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.trace + +interface AnalyticReporter { + fun log(key: String, params: Map<String, String>) + + fun log(key: String) + + fun login(id: String) +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/FirebaseAnalyticReporter.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/FirebaseAnalyticReporter.kt new file mode 100644 index 0000000000..5cbdc23c3b --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/trace/FirebaseAnalyticReporter.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.trace + +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.analytics.logEvent +import com.google.firebase.ktx.Firebase + +class FirebaseAnalyticReporter : AnalyticReporter { + private var firebaseAnalytics: FirebaseAnalytics = Firebase.analytics + + override fun log(key: String, params: Map<String, String>) { + firebaseAnalytics.logEvent(key) { params.forEach { entry -> param(entry.key, entry.value) } } + } + + override fun log(key: String) { + firebaseAnalytics.logEvent(key, null) + } + + override fun login(id: String) { + log(AnalyticsKeys.LOGIN) + } +} + +object AnalyticsKeys { + const val LOGIN = "login" + const val SEARCH = "search" + const val TRANSFER_OUT = "transfer_out" +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SystemConstants.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SystemConstants.kt index d439ef5dc6..02a1fc9d2d 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SystemConstants.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SystemConstants.kt @@ -31,6 +31,7 @@ object SystemConstants { const val OBSERVATION_CODE_SYSTEM = "https://d-tree.org/fhir/observation-codes" const val CARE_PLAN_REFERENCE_SYSTEM = "https://d-tree.org/fhir/careplan-reference" const val QUESTIONNAIRE_REFERENCE_SYSTEM = "https://d-tree.org/fhir/procedure-code" + const val LOCATION_TAG = "http://smartregister.org/fhir/location-tag" } object ReasonConstants { @@ -53,6 +54,9 @@ object ReasonConstants { var interruptedTreatmentTracingCode = Coding(SystemConstants.REASON_CODE_SYSTEM, "interrupted-treatment", "Interrupted Treatment") + var pendingTransferOutCode = + Coding("https://d-tree.org/fhir/transfer-out-status", "pending", "Pending") + const val TRACING_OUTCOME_CODE = "tracing-outcome" const val DATE_OF_AGREED_APPOINTMENT = "date-of-agreed-appointment" } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirEngineExtension.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirEngineExtension.kt index 0fcff19cbc..3baca8b803 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirEngineExtension.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirEngineExtension.kt @@ -21,6 +21,7 @@ import ca.uhn.fhir.util.UrlUtil import com.google.android.fhir.FhirEngine import com.google.android.fhir.db.ResourceNotFoundException import com.google.android.fhir.get +import com.google.android.fhir.logicalId import com.google.android.fhir.search.Operation import com.google.android.fhir.search.SearchQuery import com.google.android.fhir.search.filter.TokenParamFilterCriterion @@ -116,3 +117,20 @@ suspend inline fun <reified R : Resource> FhirEngine.getResourcesByIds( } .map { it.resource } } + +suspend fun FhirEngine.forceTagsUpdate(source: Resource) { + try { + var resource = source + /** Increment [Resource.meta] versionId of [source]. */ + resource.meta.versionId?.toInt()?.plus(1)?.let { + /** Assign [Resource.meta] versionId of [source]. */ + resource = resource.copy().apply { meta.versionId = "$it" } + /** Delete a FHIR [source] in the local storage. */ + this.purge(resource.resourceType, resource.logicalId, forcePurge = true) + /** Recreate a FHIR [source] in the local storage. */ + this.create(resource) + } + } catch (e: Exception) { + Timber.e(e) + } +} diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestApplication.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestApplication.kt index 92bdbaee40..82d76a3e91 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestApplication.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestApplication.kt @@ -30,6 +30,7 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration import com.github.anrwatchdog.ANRWatchDog import com.google.android.fhir.datacapture.DataCaptureConfig +import com.google.firebase.analytics.ktx.analytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase import com.google.firebase.perf.ktx.performance @@ -93,6 +94,7 @@ class QuestApplication : } else { Firebase.performance.isPerformanceCollectionEnabled = false Firebase.crashlytics.setCrashlyticsCollectionEnabled(false) + Firebase.analytics.setAnalyticsCollectionEnabled(false) Timber.plant(Timber.DebugTree()) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/MainNavigationScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/MainNavigationScreen.kt index 33f06fc4b3..4c8222a7f4 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/MainNavigationScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/MainNavigationScreen.kt @@ -68,6 +68,8 @@ sealed class MainNavigationScreen( route = "tracingProfileRoute", ) + object TransferOut : MainNavigationScreen(route = "transferOut") + object PatientGuardians : MainNavigationScreen(route = "patientProfileGuardians") object FamilyProfile : MainNavigationScreen(route = "familyProfileRoute") @@ -94,6 +96,7 @@ sealed class MainNavigationScreen( FamilyProfile, ViewChildContacts, GuardianProfile, + TransferOut, TracingProfile, TracingHistory, TracingOutcomes, diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt index b63bfdf8e6..e31283d4f2 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt @@ -158,7 +158,7 @@ open class AppMainActivity : BaseMultiLanguageActivity(), OnSyncListener { schedulePlan(applicationContext) scheduleCheckForMissedAppointments(applicationContext) scheduleWelcomeServiceAppointments(applicationContext) -// scheduleWelcomeServiceToCarePlanForMissedAppointments(applicationContext) + // scheduleWelcomeServiceToCarePlanForMissedAppointments(applicationContext) scheduleResourcePurger(applicationContext) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainScreen.kt index e414472d21..2219269f25 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainScreen.kt @@ -49,6 +49,7 @@ import org.smartregister.fhircore.quest.ui.patient.profile.PatientProfileScreen import org.smartregister.fhircore.quest.ui.patient.profile.childcontact.ChildContactsProfileScreen import org.smartregister.fhircore.quest.ui.patient.profile.guardians.GuardianRelatedPersonProfileScreen import org.smartregister.fhircore.quest.ui.patient.profile.guardians.GuardiansRoute +import org.smartregister.fhircore.quest.ui.patient.profile.tranfer.TransferOutScreen import org.smartregister.fhircore.quest.ui.patient.register.PatientRegisterScreen import org.smartregister.fhircore.quest.ui.report.measure.MeasureReportViewModel import org.smartregister.fhircore.quest.ui.report.measure.measureReportNavigationGraph @@ -213,6 +214,18 @@ private fun AppMainNavigationGraph( onBackPress = { navController.popBackStack() }, ) } + MainNavigationScreen.TransferOut -> + composable( + route = "${it.route}/{${NavigationArg.PATIENT_ID}}", + arguments = + commonNavArgs.plus( + listOf( + navArgument(NavigationArg.PATIENT_ID) { type = NavType.StringType }, + ), + ), + ) { _ -> + TransferOutScreen { navController.popBackStack() } + } MainNavigationScreen.GuardianProfile -> composable( route = 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 0d5a22da94..d4a29062d1 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 @@ -302,14 +302,26 @@ constructor( questionnaireType = QuestionnaireType.DEFAULT, populationResources = profile.populationResources, ) - R.id.patient_transfer_out -> - QuestionnaireActivity.launchQuestionnaire( - event.context, - questionnaireId = PATIENT_TRANSFER_OUT, - clientIdentifier = patientId, - questionnaireType = QuestionnaireType.DEFAULT, - populationResources = profile.populationResources, - ) + R.id.patient_transfer_out -> { + // QuestionnaireActivity.launchQuestionnaire( + // event.context, + // questionnaireId = PATIENT_TRANSFER_OUT, + // clientIdentifier = patientId, + // questionnaireType = QuestionnaireType.DEFAULT, + // populationResources = profile.populationResources, + // ) + patientId.let { + val urlParams = + NavigationArg.bindArgumentsOf( + Pair(NavigationArg.FEATURE, AppFeature.PatientManagement.name), + Pair(NavigationArg.HEALTH_MODULE, HealthModule.HIV), + Pair(NavigationArg.PATIENT_ID, it), + ) + event.navController.navigate( + route = "${MainNavigationScreen.TransferOut.route}/$it$urlParams", + ) + } + } R.id.patient_change_status -> QuestionnaireActivity.launchQuestionnaire( event.context, diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreen.kt new file mode 100644 index 0000000000..c361e96fb7 --- /dev/null +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreen.kt @@ -0,0 +1,249 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.quest.ui.patient.profile.tranfer + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +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.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.ParagraphStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextIndent +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import kotlin.text.Typography.bullet +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.ui.appsetting.getMessageFromException +import org.smartregister.fhircore.engine.ui.login.LOGIN_ERROR_TEXT_TAG +import org.smartregister.fhircore.quest.R + +@Composable +fun TransferOutScreen( + viewModel: TransferOutViewModel = hiltViewModel(), + onBackPress: () -> Boolean, +) { + val state by viewModel.state.collectAsState() + val uploadState by viewModel.updateState.collectAsState() + val context = LocalContext.current + + LaunchedEffect(uploadState) { + if (uploadState is DataLoadState.Success) { + onBackPress() + onBackPress() + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(id = R.string.transfer_out)) }, + navigationIcon = { + IconButton(onClick = { onBackPress() }) { + Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) + } + }, + ) + }, + ) { innerPadding -> + Box( + modifier = Modifier.padding(innerPadding).fillMaxSize(), + ) { + when (state) { + is DataLoadState.Success -> { + val data = (state as DataLoadState.Success<TransferOutScreenState>).data + TransferOutScreenContainer(data, uploadState) { viewModel.transferPatient(context) } + } + is DataLoadState.Error -> { + Column( + Modifier.fillMaxWidth().align(Alignment.Center), + ) { + Text( + fontSize = 14.sp, + color = MaterialTheme.colors.error, + text = + stringResource( + id = getMessageFromException((state as DataLoadState.Error).exception), + ), + modifier = + Modifier.wrapContentWidth() + .padding(vertical = 10.dp) + .align(Alignment.CenterHorizontally) + .testTag(LOGIN_ERROR_TEXT_TAG), + ) + Button(onClick = { viewModel.fetchPatientDetails() }) { Text(text = "Retry") } + } + } + else -> { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.align(Alignment.Center), + ) { + CircularProgressIndicator( + modifier = Modifier.padding(bottom = 16.dp), + strokeWidth = 1.6.dp, + ) + } + } + } + } + } +} + +@Composable +fun TransferOutScreenContainer( + data: TransferOutScreenState, + uploadState: DataLoadState<Boolean>, + onClick: () -> Unit, +) { + val paragraphStyle = ParagraphStyle(textIndent = TextIndent(restLine = 12.sp)) + val steps = + listOf( + "Click Transfer out button", + "A screen for writing emails will appear for you to fill in the transfer out request", + "Fill in the name of the facility the patient is transferring to in the email", + "Fill in any additional information in the email", + "Send the email", + ) + Column( + Modifier.fillMaxSize().padding(28.dp), + ) { + Text( + text = + buildAnnotatedString { + append("Are you sure you want to transfer ") + withStyle( + style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold).toSpanStyle(), + ) { + append(data.fullName) + } + append(" to a new facility?") + }, + style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Normal), + ) + Spacer(Modifier.height(12.dp)) + Text( + text = + buildAnnotatedString { + appendLine("To transfer out a patient, carry out the following steps") + steps.forEach { + withStyle(style = paragraphStyle) { + append(bullet) + append("\t\t") + append(it) + } + } + appendLine() + appendLine() + withStyle( + style = + MaterialTheme.typography.subtitle2 + .copy( + color = MaterialTheme.colors.error, + ) + .toSpanStyle(), + ) { + appendLine( + "Note: Once the process starts the patient will not be accessible. The transfer out process is not immediate and might take up to 24 hours before the client is accessible at the target facility", + ) + } + }, + ) + Spacer(Modifier.height(10.dp)) + if (uploadState is DataLoadState.Error) { + Box( + modifier = + Modifier.fillMaxWidth() + .background( + color = MaterialTheme.colors.error, + shape = RoundedCornerShape(12.dp), + ) + .padding(12.dp), + ) { + Text( + text = "Something went wrong while, transferring out the patient, please try again..", + color = MaterialTheme.colors.onError, + ) + } + Spacer(Modifier.height(10.dp)) + } + + Button( + onClick = onClick, + modifier = Modifier.fillMaxWidth(), + enabled = uploadState !is DataLoadState.Loading, + ) { + if (uploadState is DataLoadState.Loading) { + CircularProgressIndicator() + Spacer(modifier = Modifier.width(6.dp)) + Text(text = "Preparing information....") + } else { + Text(text = "Transfer Out") + } + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun TransferOutScreenContainerPreview() { + val data = + TransferOutScreenState( + "", + "Maliko", + "", + "Maliko Samuel", + ) + Scaffold { innerPadding -> + Box( + modifier = Modifier.padding(innerPadding).fillMaxSize(), + ) { + TransferOutScreenContainer(data, DataLoadState.Loading) {} + } + } +} diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreenState.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreenState.kt new file mode 100644 index 0000000000..7779f8009f --- /dev/null +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutScreenState.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.quest.ui.patient.profile.tranfer + +import com.google.android.fhir.logicalId +import org.hl7.fhir.r4.model.Patient +import org.smartregister.fhircore.engine.util.SystemConstants +import org.smartregister.fhircore.engine.util.extension.extractGivenName +import org.smartregister.fhircore.engine.util.extension.extractName + +data class TransferOutScreenState( + val patientId: String, + val facilityId: String, + val firstName: String, + val fullName: String, +) { + companion object { + fun fromPatient(patient: Patient): TransferOutScreenState { + return TransferOutScreenState( + patientId = patient.logicalId, + facilityId = + patient.meta.tag.firstOrNull { it.system == SystemConstants.LOCATION_TAG }?.code ?: "NA", + firstName = patient.extractGivenName(), + fullName = patient.extractName(), + ) + } + } +} diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt new file mode 100644 index 0000000000..79df3e7704 --- /dev/null +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt @@ -0,0 +1,160 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.quest.ui.patient.profile.tranfer + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.fhir.FhirEngine +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.Patient +import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry +import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration +import org.smartregister.fhircore.engine.data.local.DefaultRepository +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.sync.SyncBroadcaster +import org.smartregister.fhircore.engine.trace.AnalyticReporter +import org.smartregister.fhircore.engine.trace.AnalyticsKeys +import org.smartregister.fhircore.engine.util.ReasonConstants +import org.smartregister.fhircore.engine.util.SharedPreferenceKey +import org.smartregister.fhircore.engine.util.SharedPreferencesHelper +import org.smartregister.fhircore.engine.util.extension.forceTagsUpdate +import org.smartregister.fhircore.quest.navigation.NavigationArg +import timber.log.Timber + +@HiltViewModel +class TransferOutViewModel +@Inject +constructor( + savedStateHandle: SavedStateHandle, + private val defaultRepository: DefaultRepository, + private val fhirEngine: FhirEngine, + private val analytics: AnalyticReporter, + private val sharedPreferencesHelper: SharedPreferencesHelper, + configurationRegistry: ConfigurationRegistry, + private val syncBroadcaster: SyncBroadcaster + +) : ViewModel() { + private val patientId: String = savedStateHandle[NavigationArg.PATIENT_ID]!! + val state = MutableStateFlow<DataLoadState<TransferOutScreenState>>(DataLoadState.Loading) + val updateState = MutableStateFlow<DataLoadState<Boolean>>(DataLoadState.Idle) + + private val applicationConfiguration: ApplicationConfiguration = + configurationRegistry.getAppConfigs() + + private val currentPractitioner by lazy { + sharedPreferencesHelper.read( + key = SharedPreferenceKey.PRACTITIONER_ID.name, + defaultValue = null, + ) + } + + init { + fetchPatientDetails() + } + + fun fetchPatientDetails() { + viewModelScope.launch(Dispatchers.IO) { + try { + state.value = DataLoadState.Loading + val patient: Patient? = defaultRepository.loadResource<Patient>(patientId) + if (patient == null) { + state.value = DataLoadState.Error(java.lang.Exception("Failed to load patient")) + return@launch + } + state.value = DataLoadState.Success(TransferOutScreenState.fromPatient(patient)) + } catch (e: Exception) { + state.value = DataLoadState.Error(e) + } + } + } + + fun transferPatient(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + try { + updateState.value = DataLoadState.Loading + val value = state.value + if (value !is DataLoadState.Success) { + updateState.value = DataLoadState.Error(java.lang.Exception()) + return@launch + } + val data = value.data + val patient = defaultRepository.loadResource<Patient>(data.patientId) + if (patient == null) { + updateState.value = DataLoadState.Error(java.lang.Exception()) + return@launch + } + patient.active = false + val meta = patient.meta + meta.addTag(ReasonConstants.pendingTransferOutCode) + patient.meta = meta + fhirEngine.forceTagsUpdate(patient) + analytics.log( + AnalyticsKeys.TRANSFER_OUT, + mapOf(Pair("patient", patient.id), Pair("practitioner", currentPractitioner ?: "")), + ) + val email = applicationConfiguration.supportEmail + val subject = "Request for Patient Transfer out for ${data.fullName}" + val body = + """ + Where is the patient transferring to? + + + + Do you have any additional information you want to add? + + + + --------- Do not edit below this line --------- + Current facility: ${data.facilityId} + Patient id: ${data.patientId} + Practitioner: $currentPractitioner + """ + .trimIndent() + composeEmail(context, arrayOf(email), subject, body) + updateState.value = DataLoadState.Success(true) + syncBroadcaster.runSync() + } catch (e: Exception) { + Timber.e(e) + updateState.value = DataLoadState.Error(e) + } + } + } +} + +private fun composeEmail( + context: Context, + addresses: Array<String>, + subject: String, + body: String, +) { + val intent = + Intent(Intent.ACTION_SENDTO).apply { + setData(Uri.parse("mailto:")) + putExtra(Intent.EXTRA_EMAIL, addresses) + putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, body) + } + context.startActivity(intent) +} diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/models/ProfileViewData.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/models/ProfileViewData.kt index b9ec1d91d4..7e2b85a71d 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/models/ProfileViewData.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/models/ProfileViewData.kt @@ -27,7 +27,6 @@ import org.hl7.fhir.r4.model.Task import org.smartregister.fhircore.engine.data.domain.Guardian import org.smartregister.fhircore.engine.domain.model.FormButtonData import org.smartregister.fhircore.engine.domain.model.TracingAttempt -import org.smartregister.fhircore.engine.util.extension.extractedTracingCategoryIsPhone import org.smartregister.fhircore.quest.ui.family.profile.model.FamilyMemberViewState sealed class ProfileViewData(