Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ suspend fun Questionnaire.prepopulateWithComputedConfigValues(
?: questionnaireConfig.linkIds?.firstOrNull { it.type == LinkIdType.BARCODE }?.linkId)
?.let { barcodeLinkId ->
find(barcodeLinkId)?.apply {
if (hasExtension(EXTENSION_INITIAL_EXPRESSION_URL)) {
removeExtension(EXTENSION_INITIAL_EXPRESSION_URL)
}

initial =
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent()
Expand Down Expand Up @@ -285,6 +289,10 @@ suspend fun Questionnaire.prepopulateUniqueIdAssignment(
questionnaireComputedValues,
)
if (extractedId.isNotEmpty()) {
if (hasExtension(EXTENSION_INITIAL_EXPRESSION_URL)) {
removeExtension(EXTENSION_INITIAL_EXPRESSION_URL)
}

initial =
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent().setValue(StringType(extractedId)),
Expand Down
8 changes: 4 additions & 4 deletions android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ dokkaBase = "1.9.20"
easyRulesCore = "4.1.1-SNAPSHOT"
espresso-core = "3.6.1"
fhir-sdk-common = "0.1.0-alpha05-preview3-SNAPSHOT"
fhir-sdk-contrib-barcode = "0.1.0-beta3-preview7-SNAPSHOT"
fhir-sdk-contrib-locationwidget = "0.1.0-alpha01-preview2-SNAPSHOT"
fhir-sdk-data-capture = "1.3.0-preview-SNAPSHOT"
fhir-sdk-engine = "1.2.0-preview-SNAPSHOT"
fhir-sdk-contrib-barcode = "0.1.0-beta3-preview8-SNAPSHOT"
fhir-sdk-contrib-locationwidget = "0.1.0-alpha01-preview3-SNAPSHOT"
fhir-sdk-data-capture = "1.3.0-preview9-SNAPSHOT"
fhir-sdk-engine = "1.2.0-preview1-SNAPSHOT"
fhir-sdk-knowledge = "0.1.0-beta01-preview1-SNAPSHOT"
fhir-sdk-workflow = "0.1.0-beta01-preview2-SNAPSHOT"
generativeai = "0.9.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,9 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
)
}
?.let { setQuestionnaireResponse(questionnaireResponse.json()) }
?: showToast(getString(R.string.error_populating_questionnaire))
?: withContext(dispatcherProvider.main()) {
showToast(getString(R.string.error_populating_questionnaire))
}
}

launchContextResources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,12 +1164,6 @@ constructor(
launchContextResources(resourceType, resourceIdentifier, actionParameters)
val launchContexts = launchContextResources.associateBy { it.resourceType.name.lowercase() }

// Populate questionnaire with initial default values
ResourceMapper.populate(
questionnaire,
launchContexts = launchContexts,
)

questionnaire.prepopulateWithComputedConfigValues(
questionnaireConfig,
actionParameters,
Expand All @@ -1194,82 +1188,118 @@ constructor(
},
)

// Populate questionnaire with latest QuestionnaireResponse
// Populate questionnaire with latest QuestionnaireResponse and initial default values
val questionnaireResponse =
if (
resourceType != null &&
!resourceIdentifier.isNullOrEmpty() &&
(questionnaireConfig.isEditable() ||
questionnaireConfig.isReadOnly() ||
questionnaireConfig.isSummary() ||
questionnaireConfig.saveDraft)
) {
defaultRepository
.searchQuestionnaireResponse(
resourceId = resourceIdentifier,
resourceType = resourceType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),
)
?.let {
QuestionnaireResponse().apply {
/**
* Only set the ID value when [saveDraft] is set to true] this ensues that a new
* [QuestionnaireResponse] is generated when the questionnaire is submitted
*/
if (questionnaireConfig.saveDraft) {
id = it.id
}
status = it.status
item = it.item.removeUnAnsweredItems()
// Clearing the text prompts the SDK to re-process the content, which includes
// HTML
clearText()
}
}
} else {
null
}
fetchRepositoryQuestionnaireResponse(
questionnaireConfig,
resourceIdentifier,
resourceType,
questionnaire,
defaultQuestionnaire =
withContext(dispatcherProvider.default()) {
ResourceMapper.populate(
questionnaire,
launchContexts = launchContexts,
)
},
)

// Exclude the configured fields from QR
if (questionnaireResponse != null) {
val exclusionLinkIdsMap: Map<String, Boolean> =
questionnaireConfig.linkIds
?.asSequence()
?.filter { it.type == LinkIdType.PREPOPULATION_EXCLUSION }
?.associateBy { it.linkId }
?.mapValues { it.value.type == LinkIdType.PREPOPULATION_EXCLUSION } ?: emptyMap()

questionnaireResponse.item =
excludePrepopulationFields(
questionnaireResponse.item.toMutableList(),
exclusionLinkIdsMap,
questionnaireResponse.apply { item = item.removeUnAnsweredItems() }

Pair(questionnaireResponse, launchContextResources)
}

private suspend fun fetchRepositoryQuestionnaireResponse(
questionnaireConfig: QuestionnaireConfig,
resourceIdentifier: String?,
resourceType: ResourceType?,
questionnaire: Questionnaire,
defaultQuestionnaire: QuestionnaireResponse,
): QuestionnaireResponse {
if (resourceType == null || resourceIdentifier.isNullOrEmpty()) return defaultQuestionnaire

val searchQR =
questionnaireConfig
.takeIf { it.isEditable() || it.isReadOnly() || it.isSummary() || it.saveDraft }
?.let {
defaultRepository.searchQuestionnaireResponse(
resourceId = resourceIdentifier,
resourceType = resourceType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),
)
}

if (searchQR == null) return defaultQuestionnaire

val exclusionLinkIdsMap: Map<String, Boolean> =
questionnaireConfig.linkIds
?.asSequence()
?.filter { it.type == LinkIdType.PREPOPULATION_EXCLUSION }
?.associateBy { it.linkId }
?.mapValues { it.value.type == LinkIdType.PREPOPULATION_EXCLUSION } ?: emptyMap()

return QuestionnaireResponse().apply {
/**
* Only set the ID value when [saveDraft] is set to true] this ensues that a new
* [QuestionnaireResponse] is generated when the questionnaire is submitted
*/
if (questionnaireConfig.saveDraft) {
id = searchQR.id
}
status = searchQR.status

Pair(questionnaireResponse, launchContextResources)
// Exclude the configured fields from QR
item =
excludePreviouslyAnsweredFields(
searchQR.item,
defaultQuestionnaire.item,
exclusionLinkIdsMap,
)

// Clearing the text prompts the SDK to re-process the content, which includes
// HTML
clearText()
}
}

fun excludePrepopulationFields(
items: MutableList<QuestionnaireResponseItemComponent>,
fun excludePreviouslyAnsweredFields(
previouslyAnsweredItems: MutableList<QuestionnaireResponseItemComponent>,
newlyPopulatedItems: List<QuestionnaireResponseItemComponent>,
exclusionMap: Map<String, Boolean>,
): MutableList<QuestionnaireResponseItemComponent> {
val stack = LinkedList<MutableList<QuestionnaireResponseItemComponent>>()
stack.push(items)
while (stack.isNotEmpty()) {
val currentItems = stack.pop()
val iterator = currentItems.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (exclusionMap.containsKey(item.linkId)) {
iterator.remove()
} else if (item.item.isNotEmpty()) {
stack.push(item.item)
val prevStack =
LinkedList<MutableList<QuestionnaireResponseItemComponent>>().apply {
push(previouslyAnsweredItems)
}
val newlyStack =
LinkedList<List<QuestionnaireResponseItemComponent>>().apply { push(newlyPopulatedItems) }

while (prevStack.isNotEmpty()) {
val prevAnswerItemsIterator = prevStack.pop().listIterator()
val newItemsIterator: Iterator<QuestionnaireResponseItemComponent> =
(if (newlyStack.isNotEmpty()) newlyStack.pop() else emptyList()).iterator()

while (prevAnswerItemsIterator.hasNext()) {
val item = prevAnswerItemsIterator.next()
val newPopulatedItem = if (newItemsIterator.hasNext()) newItemsIterator.next() else null

when {
exclusionMap.containsKey(item.linkId) && newPopulatedItem?.linkId == item.linkId -> {
prevAnswerItemsIterator.set(newPopulatedItem)
}
exclusionMap.containsKey(item.linkId) -> {
prevAnswerItemsIterator.remove()
}
else -> {
prevStack.push(item.item)
newPopulatedItem?.let { newlyStack.push(it.item) }
}
}
}
}
return items
return previouslyAnsweredItems
}

private fun List<QuestionnaireResponseItemComponent>.removeUnAnsweredItems():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ import com.google.android.fhir.datacapture.extensions.itemControlCode
import com.google.android.fhir.datacapture.extensions.tryUnwrapContext
import com.google.android.fhir.datacapture.views.HeaderView
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderDelegate
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemAndroidViewHolderDelegate
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemAndroidViewHolderFactory
import com.google.android.material.textfield.TextInputEditText
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.StringType

object PasswordViewHolderFactory :
QuestionnaireItemViewHolderFactory(
QuestionnaireItemAndroidViewHolderFactory(
org.smartregister.fhircore.quest.R.layout.password_view,
) {
override fun getQuestionnaireItemViewHolderDelegate() =
object : QuestionnaireItemViewHolderDelegate {
object : QuestionnaireItemAndroidViewHolderDelegate {
override lateinit var questionnaireViewItem: QuestionnaireViewItem

private lateinit var header: HeaderView
Expand All @@ -58,7 +58,7 @@ object PasswordViewHolderFactory :

passwordEditText =
itemView
.findViewById<TextInputEditText?>(
.findViewById<TextInputEditText>(
org.smartregister.fhircore.quest.R.id.password_edit_text,
)
.apply {
Expand Down
Loading
Loading