diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt
index c4abca4b5e1..d5a25304d46 100644
--- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt
+++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt
@@ -67,6 +67,9 @@ constructor(
private val authConfiguration by lazy { configService.provideAuthConfiguration() }
private var isLoginPageRendered = false
+ /** When true, suppresses automatic LoginActivity launch (e.g., during questionnaire filling) */
+ var suppressLoginRedirect = false
+
fun getAccessToken(): String {
val account = findAccount() ?: return ""
val accessToken = accountManager.peekAuthToken(account, AUTH_TOKEN_TYPE) ?: ""
@@ -118,12 +121,16 @@ constructor(
bundle.containsKey(AccountManager.KEY_INTENT) -> {
val launchIntent = bundle.get(AccountManager.KEY_INTENT) as? Intent
- // Deletes session PIN to allow reset
- secureSharedPreference.deleteSessionPin()
+ if (suppressLoginRedirect) {
+ Timber.w("Login redirect suppressed — questionnaire in progress")
+ } else {
+ // Deletes session PIN to allow reset
+ secureSharedPreference.deleteSessionPin()
- if (launchIntent != null && !isLoginPageRendered) {
- context.startActivity(launchIntent.putExtra(CANCEL_BACKGROUND_SYNC, true))
- isLoginPageRendered = true
+ if (launchIntent != null && !isLoginPageRendered) {
+ context.startActivity(launchIntent.putExtra(CANCEL_BACKGROUND_SYNC, true))
+ isLoginPageRendered = true
+ }
}
}
}
diff --git a/android/engine/src/main/res/values/strings.xml b/android/engine/src/main/res/values/strings.xml
index ec06e3d0b9f..3a3464dfb2f 100644
--- a/android/engine/src/main/res/values/strings.xml
+++ b/android/engine/src/main/res/values/strings.xml
@@ -121,6 +121,7 @@
%
Logging out. Please wait…
Session has been expired and must login again
+ Your form has been saved successfully. Your session has expired, please log in again to continue.
Sex
Age
DOB
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 6d29524f456..e746d1f189e 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
@@ -40,6 +40,7 @@ import io.sentry.android.navigation.SentryNavigationListener
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -47,6 +48,7 @@ import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions
+import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator
import org.smartregister.fhircore.engine.datastore.ProtoDataStore
import org.smartregister.fhircore.engine.domain.model.LauncherType
import org.smartregister.fhircore.engine.rulesengine.services.LocationCoordinate
@@ -58,6 +60,7 @@ import org.smartregister.fhircore.engine.ui.base.AlertDialogue
import org.smartregister.fhircore.engine.ui.base.AlertIntent
import org.smartregister.fhircore.engine.ui.base.BaseMultiLanguageActivity
import org.smartregister.fhircore.engine.util.DispatcherProvider
+import org.smartregister.fhircore.engine.util.extension.launchActivityWithNoBackStackHistory
import org.smartregister.fhircore.engine.util.extension.parcelable
import org.smartregister.fhircore.engine.util.extension.serializable
import org.smartregister.fhircore.engine.util.extension.showToast
@@ -66,6 +69,7 @@ import org.smartregister.fhircore.engine.util.location.PermissionUtils
import org.smartregister.fhircore.quest.R
import org.smartregister.fhircore.quest.event.AppEvent
import org.smartregister.fhircore.quest.event.EventBus
+import org.smartregister.fhircore.quest.ui.login.LoginActivity
import org.smartregister.fhircore.quest.ui.questionnaire.QuestionnaireActivity
import org.smartregister.fhircore.quest.ui.shared.ActivityOnResultType
import org.smartregister.fhircore.quest.ui.shared.ON_RESULT_TYPE
@@ -84,6 +88,8 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
@Inject lateinit var dispatcherProvider: DispatcherProvider
+ @Inject lateinit var tokenAuthenticator: TokenAuthenticator
+
val appMainViewModel by viewModels()
private val sentryNavListener =
SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true)
@@ -110,12 +116,20 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
activityResult: ActivityResult ->
val onResultType = activityResult.data?.extras?.getString(ON_RESULT_TYPE)
- if (
- activityResult.resultCode == Activity.RESULT_OK &&
- !onResultType.isNullOrBlank() &&
- ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE
- ) {
- lifecycleScope.launch { onSubmitQuestionnaire(activityResult) }
+ lifecycleScope.launch {
+ if (
+ activityResult.resultCode == Activity.RESULT_OK &&
+ !onResultType.isNullOrBlank() &&
+ ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE
+ ) {
+ onSubmitQuestionnaire(activityResult)
+ }
+
+ // Check if session expired while questionnaire was open
+ if (!tokenAuthenticator.isCurrentRefreshTokenActive()) {
+ delay(3000) // Let user see data was saved
+ showSessionExpiredDialog()
+ }
}
}
@@ -312,6 +326,24 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
}
}
+ private fun showSessionExpiredDialog() {
+ AlertDialogue.showAlert(
+ context = this,
+ alertIntent = AlertIntent.CONFIRM,
+ message = getString(org.smartregister.fhircore.engine.R.string.session_expired_form_saved),
+ title = getString(org.smartregister.fhircore.engine.R.string.session_expired),
+ confirmButton =
+ AlertDialogButton(
+ listener = { dialog ->
+ dialog.dismiss()
+ launchActivityWithNoBackStackHistory()
+ },
+ text = org.smartregister.fhircore.engine.R.string.questionnaire_alert_ack_button_title,
+ ),
+ cancellable = false,
+ )
+ }
+
private fun overrideOnBackPressListener() {
onBackPressedDispatcher.addCallback(
object : OnBackPressedCallback(true) {
diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt
index 78dcaa3d4bf..ec3060ed1ef 100644
--- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt
+++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt
@@ -52,6 +52,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Resource
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions
+import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator
import org.smartregister.fhircore.engine.domain.model.ActionParameter
import org.smartregister.fhircore.engine.domain.model.isReadOnly
import org.smartregister.fhircore.engine.domain.model.isSummary
@@ -79,6 +80,8 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
@Inject lateinit var dispatcherProvider: DispatcherProvider
@Inject lateinit var fhirParser: IParser
+
+ @Inject lateinit var tokenAuthenticator: TokenAuthenticator
val viewModel by viewModels()
private lateinit var questionnaireConfig: QuestionnaireConfig
private lateinit var actionParameters: ArrayList
@@ -142,6 +145,8 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
return
}
+ tokenAuthenticator.suppressLoginRedirect = true
+
viewBinding.questionnaireToolbar.setNavigationIcon(R.drawable.ic_cancel)
viewBinding.questionnaireToolbar.setNavigationOnClickListener { handleBackPress() }
viewBinding.questionnaireTitle.text = questionnaireConfig.title
@@ -607,6 +612,11 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
(supportFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) as QuestionnaireFragment?)
?.getQuestionnaireResponse()
+ override fun onDestroy() {
+ tokenAuthenticator.suppressLoginRedirect = false
+ super.onDestroy()
+ }
+
companion object {
const val QUESTIONNAIRE_FRAGMENT_TAG = "questionnaireFragment"