Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for displaying display items based on conditions. #1898

Merged
merged 7 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -627,7 +627,13 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
answersChangedCallback = answersChangedCallback,
resolveAnswerValueSet = { resolveAnswerValueSet(it) },
resolveAnswerExpression = { resolveAnswerExpression(it) },
draftAnswer = draftAnswerMap[questionnaireResponseItem]
draftAnswer = draftAnswerMap[questionnaireResponseItem],
enabledDisplayItems =
questionnaireItem.item.filter {
it.type == Questionnaire.QuestionnaireItemType.DISPLAY &&
(it.isInstructionsCode || it.isFlyoverCode || it.isHelpCode) &&
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
EnablementEvaluator(questionnaireResponse).evaluate(it, questionnaireResponseItem)
}
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal class EnablementEvaluator(val questionnaireResponse: QuestionnaireRespo
if (enableWhenList.size == 1) {
return evaluateEnableWhen(
enableWhenList.single(),
questionnaireItem,
questionnaireResponseItem,
)
}
Expand All @@ -124,9 +125,9 @@ internal class EnablementEvaluator(val questionnaireResponse: QuestionnaireRespo
// enabled if ANY `enableWhen` constraint is satisfied.
return when (val value = questionnaireItem.enableBehavior) {
Questionnaire.EnableWhenBehavior.ALL ->
enableWhenList.all { evaluateEnableWhen(it, questionnaireResponseItem) }
enableWhenList.all { evaluateEnableWhen(it, questionnaireItem, questionnaireResponseItem) }
Questionnaire.EnableWhenBehavior.ANY ->
enableWhenList.any { evaluateEnableWhen(it, questionnaireResponseItem) }
enableWhenList.any { evaluateEnableWhen(it, questionnaireItem, questionnaireResponseItem) }
else -> throw IllegalStateException("Unrecognized enable when behavior $value")
}
}
Expand All @@ -136,10 +137,15 @@ internal class EnablementEvaluator(val questionnaireResponse: QuestionnaireRespo
*/
private fun evaluateEnableWhen(
enableWhen: Questionnaire.QuestionnaireItemEnableWhenComponent,
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
): Boolean {
val targetQuestionnaireResponseItem =
findEnableWhenQuestionnaireResponseItem(questionnaireResponseItem, enableWhen.question)
val targetQuestionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent? =
if (questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY &&
questionnaireResponseItem.linkId == enableWhen.question
)
questionnaireResponseItem
else findEnableWhenQuestionnaireResponseItem(questionnaireResponseItem, enableWhen.question)
return if (Questionnaire.QuestionnaireItemOperator.EXISTS == enableWhen.operator) {
// True iff the answer value of the enable when is equal to whether an answer exists in the
// target questionnaire response item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,11 @@ val Questionnaire.QuestionnaireItemComponent.localizedPrefixSpanned: Spanned?
* code is used as the instructions of the parent question.
*/
internal val Questionnaire.QuestionnaireItemComponent.localizedInstructionsSpanned: Spanned?
get() = item.localizedInstructionsSpanned

internal val List<Questionnaire.QuestionnaireItemComponent>.localizedInstructionsSpanned: Spanned?
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
get() {
return item
.firstOrNull { questionnaireItem ->
return this.firstOrNull { questionnaireItem ->
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY &&
questionnaireItem.isInstructionsCode
}
Expand All @@ -322,9 +324,11 @@ internal val Questionnaire.QuestionnaireItemComponent.localizedInstructionsSpann
* present) is used as the fly-over text of the parent question.
*/
internal val Questionnaire.QuestionnaireItemComponent.localizedFlyoverSpanned: Spanned?
get() = item.localizedFlyoverSpanned

internal val List<Questionnaire.QuestionnaireItemComponent>.localizedFlyoverSpanned: Spanned?
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
get() =
item
.firstOrNull { questionnaireItem ->
this.firstOrNull { questionnaireItem ->
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY &&
questionnaireItem.displayItemControl == DisplayItemControlType.FLYOVER
}
Expand All @@ -335,9 +339,11 @@ internal val Questionnaire.QuestionnaireItemComponent.localizedFlyoverSpanned: S
* code is used as the instructions of the parent question.
*/
internal val Questionnaire.QuestionnaireItemComponent.localizedHelpSpanned: Spanned?
get() = item.localizedHelpSpanned

internal val List<Questionnaire.QuestionnaireItemComponent>.localizedHelpSpanned: Spanned?
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
get() {
return item
.firstOrNull { questionnaireItem -> questionnaireItem.isHelpCode }
return this.firstOrNull { questionnaireItem -> questionnaireItem.isHelpCode }
?.localizedTextSpanned
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.localizedInstructionsSpanned
import com.google.android.fhir.datacapture.localizedPrefixSpanned
import com.google.android.fhir.datacapture.localizedTextSpanned
import org.hl7.fhir.r4.model.Questionnaire

internal class GroupHeaderView(context: Context, attrs: AttributeSet?) :
LinearLayout(context, attrs) {
Expand All @@ -34,14 +33,16 @@ internal class GroupHeaderView(context: Context, attrs: AttributeSet?) :
LayoutInflater.from(context).inflate(R.layout.group_type_header_view, this, true)
}

fun bind(questionnaireItem: Questionnaire.QuestionnaireItemComponent) {
fun bind(questionnaireViewItem: QuestionnaireViewItem) {
val prefix = findViewById<TextView>(R.id.prefix)
val question = findViewById<TextView>(R.id.question)
val hint = findViewById<TextView>(R.id.hint)
initHelpButton(this, questionnaireItem)
prefix.updateTextAndVisibility(questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(questionnaireItem.localizedInstructionsSpanned)
initHelpButton(this, questionnaireViewItem)
prefix.updateTextAndVisibility(questionnaireViewItem.questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireViewItem.questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(
questionnaireViewItem.enabledDisplayItems?.localizedInstructionsSpanned
)
visibility = getViewGroupVisibility(prefix, question, hint)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import com.google.android.fhir.datacapture.localizedInstructionsSpanned
import com.google.android.fhir.datacapture.localizedPrefixSpanned
import com.google.android.fhir.datacapture.localizedTextSpanned
import com.google.android.material.card.MaterialCardView
import org.hl7.fhir.r4.model.Questionnaire

/** View for the prefix, question, and hint of a questionnaire item. */
internal class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
Expand All @@ -47,11 +46,13 @@ internal class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout
private var hint: TextView = findViewById(R.id.hint)
private var errorTextView: TextView = findViewById(R.id.error_text_at_header)

fun bind(questionnaireItem: Questionnaire.QuestionnaireItemComponent) {
prefix.updateTextAndVisibility(questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(questionnaireItem.localizedInstructionsSpanned)
initHelpButton(this, questionnaireItem)
fun bind(questionnaireViewItem: QuestionnaireViewItem) {
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
prefix.updateTextAndVisibility(questionnaireViewItem.questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireViewItem.questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(
questionnaireViewItem.enabledDisplayItems?.localizedInstructionsSpanned
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
)
initHelpButton(this, questionnaireViewItem)
// Make the entire view GONE if there is nothing to show. This is to avoid an empty row in the
// questionnaire.
visibility = getViewGroupVisibility(prefix, question, hint)
Expand Down Expand Up @@ -93,13 +94,10 @@ internal fun getViewGroupVisibility(vararg view: TextView): Int {
return GONE
}

internal fun initHelpButton(
view: View,
questionnaireItem: Questionnaire.QuestionnaireItemComponent
) {
internal fun initHelpButton(view: View, questionnaireViewItem: QuestionnaireViewItem) {
val helpButton = view.findViewById<Button>(R.id.helpButton)
helpButton.visibility =
if (questionnaireItem.hasHelpButton) {
if (questionnaireViewItem.questionnaireItem.hasHelpButton) {
VISIBLE
} else {
GONE
Expand All @@ -118,5 +116,5 @@ internal fun initHelpButton(

view
.findViewById<TextView>(R.id.helpText)
.updateTextAndVisibility(questionnaireItem.localizedHelpSpanned)
.updateTextAndVisibility(questionnaireViewItem.enabledDisplayItems?.localizedHelpSpanned)
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ data class QuestionnaireViewItem(
{
emptyList()
},
internal val draftAnswer: Any? = null
internal val draftAnswer: Any? = null,
internal val enabledDisplayItems: List<Questionnaire.QuestionnaireItemComponent>? = null
MJ1998 marked this conversation as resolved.
Show resolved Hide resolved
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ internal object AttachmentViewHolderFactory :

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
this.questionnaireViewItem = questionnaireViewItem
header.bind(questionnaireViewItem)
val questionnaireItem = questionnaireViewItem.questionnaireItem
header.bind(questionnaireItem)
displayOrClearInitialPreview()
displayTakePhotoButton(questionnaireItem)
displayUploadButton(questionnaireItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ internal object AutoCompleteViewHolderFactory :
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)

val answerOptionString =
questionnaireViewItem.answerOption.map { it.value.displayString(header.context) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ internal object BooleanChoiceViewHolderFactory :

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
this.questionnaireViewItem = questionnaireViewItem
val questionnaireItem = questionnaireViewItem.questionnaireItem
header.bind(questionnaireItem)
header.bind(questionnaireViewItem)
val choiceOrientation =
questionnaireItem.choiceOrientation ?: ChoiceOrientationTypes.VERTICAL
questionnaireViewItem.questionnaireItem.choiceOrientation
?: ChoiceOrientationTypes.VERTICAL
with(flow) {
when (choiceOrientation) {
ChoiceOrientationTypes.HORIZONTAL -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ internal object CheckBoxGroupViewHolderFactory :
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
val questionnaireItem = questionnaireViewItem.questionnaireItem
header.bind(questionnaireViewItem)
val choiceOrientation =
questionnaireItem.choiceOrientation ?: ChoiceOrientationTypes.VERTICAL

header.bind(questionnaireItem)
questionnaireViewItem.questionnaireItem.choiceOrientation
?: ChoiceOrientationTypes.VERTICAL

// Keep the Flow layout which is always the first child
checkboxGroup.removeViews(1, checkboxGroup.childCount - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ internal object DatePickerViewHolderFactory :
@SuppressLint("NewApi") // java.time APIs can be used due to desugaring
override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
clearPreviousState()
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
textInputLayout.hint = canonicalizedDatePattern
textInputEditText.removeTextChangedListener(textWatcher)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ internal object DateTimePickerViewHolderFactory :
@SuppressLint("NewApi") // java.time APIs can be used due to desugaring
override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
clearPreviousState()
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
dateInputLayout.hint = canonicalizedDatePattern
dateInputEditText.removeTextChangedListener(textWatcher)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,28 @@ internal object QuestionnaireItemDialogSelectViewHolderFactory :

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
cleanupOldState()
holder.summaryHolder.hint = questionnaireViewItem.questionnaireItem.localizedFlyoverSpanned
holder.summaryHolder.hint =
questionnaireViewItem.enabledDisplayItems?.localizedFlyoverSpanned
val activity =
requireNotNull(holder.header.context.tryUnwrapContext()) {
"Can only use dialog select in an AppCompatActivity context"
}
val viewModel: QuestionnaireItemDialogSelectViewModel by activity.viewModels()

val item = questionnaireViewItem.questionnaireItem

// Bind static data
holder.header.bind(item)
holder.header.bind(questionnaireViewItem)

val questionnaireItem = questionnaireViewItem.questionnaireItem
selectedOptionsJob =
activity.lifecycleScope.launch {
// Set the initial selected options state from the FHIR data model
viewModel.updateSelectedOptions(
item.linkId,
questionnaireItem.linkId,
questionnaireViewItem.extractInitialOptions(holder.header.context)
)

// Listen for changes to selected options to update summary + FHIR data model
viewModel.getSelectedOptionsFlow(item.linkId).collect { selectedOptions ->
viewModel.getSelectedOptionsFlow(questionnaireItem.linkId).collect { selectedOptions ->
holder.summary.text = selectedOptions.selectedSummary
updateAnswers(selectedOptions)
}
Expand All @@ -95,12 +95,12 @@ internal object QuestionnaireItemDialogSelectViewHolderFactory :
View.OnClickListener {
val fragment =
OptionSelectDialogFragment(
title = item.localizedTextSpanned ?: "",
config = item.buildConfig(),
title = questionnaireItem.localizedTextSpanned ?: "",
config = questionnaireItem.buildConfig(),
)
fragment.arguments =
bundleOf(
OptionSelectDialogFragment.KEY_QUESTION_LINK_ID to item.linkId,
OptionSelectDialogFragment.KEY_QUESTION_LINK_ID to questionnaireItem.linkId,
)
fragment.show(activity.supportFragmentManager, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal object DisplayViewHolderFactory :
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
}

override fun setReadOnly(isReadOnly: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ internal object DropDownViewHolderFactory :

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
cleanupOldState()
header.bind(questionnaireViewItem.questionnaireItem)
textInputLayout.hint = questionnaireViewItem.questionnaireItem.localizedFlyoverSpanned
header.bind(questionnaireViewItem)
textInputLayout.hint = questionnaireViewItem.enabledDisplayItems?.localizedFlyoverSpanned
val answerOptionList =
this.questionnaireViewItem.answerOption
.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
textInputLayout.hint = questionnaireViewItem.questionnaireItem.localizedFlyoverSpanned
header.bind(questionnaireViewItem)
textInputLayout.hint = questionnaireViewItem.enabledDisplayItems?.localizedFlyoverSpanned
displayValidationResult(questionnaireViewItem.validationResult)

textInputEditText.removeTextChangedListener(textWatcher)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal object GroupViewHolderFactory :
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
addItemButton.visibility =
if (questionnaireViewItem.questionnaireItem.repeats) View.VISIBLE else View.GONE
addItemButton.setOnClickListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ internal object RadioGroupViewHolderFactory :
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
val questionnaireItem = questionnaireViewItem.questionnaireItem
header.bind(questionnaireItem)
header.bind(questionnaireViewItem)
// Keep the Flow layout which is the first child
radioGroup.removeViews(1, radioGroup.childCount - 1)
val choiceOrientation =
questionnaireItem.choiceOrientation ?: ChoiceOrientationTypes.VERTICAL
questionnaireViewItem.questionnaireItem.choiceOrientation
?: ChoiceOrientationTypes.VERTICAL
when (choiceOrientation) {
ChoiceOrientationTypes.HORIZONTAL -> {
flow.setOrientation(Flow.HORIZONTAL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ internal object ReviewViewHolderFactory : QuestionnaireItemViewHolderFactory(R.l
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
val localizedFlyoverSpanned =
questionnaireViewItem.questionnaireItem.localizedFlyoverSpanned
questionnaireViewItem.enabledDisplayItems?.localizedFlyoverSpanned
flyOverTextView.apply {
visibility =
if (localizedFlyoverSpanned.isNullOrEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal object SliderViewHolderFactory : QuestionnaireItemViewHolderFactory(R.l

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
this.questionnaireViewItem = questionnaireViewItem
header.bind(questionnaireViewItem.questionnaireItem)
header.bind(questionnaireViewItem)
val answer = questionnaireViewItem.answers.singleOrNull()
val minValue = getMinValue(questionnaireViewItem.questionnaireItem)
val maxValue = getMaxValue(questionnaireViewItem.questionnaireItem)
Expand Down
Loading