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"