diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponses.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponses.kt index fa0f4c545d..0d9b3cd194 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponses.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponses.kt @@ -67,16 +67,8 @@ private fun List.packR val questionnaireItem = questionnaireItems.single() - questionnaireResponseItems.forEach { it -> - if (questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP) { - if (questionnaireItem.repeats) { - it.answer.forEach { it.item = it.item.packRepeatedGroups(questionnaireItem.item) } - } else { - it.item = it.item.packRepeatedGroups(questionnaireItem.item) - } - } else { - it.answer.forEach { it.item = it.item.packRepeatedGroups(questionnaireItem.item) } - } + questionnaireResponseItems.forEach { + it.item = it.item.packRepeatedGroups(questionnaireItem.item) } if ( diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreTypes.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreTypes.kt index bf6a2b00bd..c51bfb8413 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreTypes.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreTypes.kt @@ -17,6 +17,7 @@ package com.google.android.fhir.datacapture.extensions import android.content.Context +import android.text.Spanned import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.views.factories.localDate import com.google.android.fhir.datacapture.views.factories.localTime @@ -57,6 +58,8 @@ fun Type.asStringValue(): String { fun Type.displayString(context: Context): String = getDisplayString(this, context) ?: context.getString(R.string.not_answered) +fun Type.displayStringSpanned(context: Context): Spanned = displayString(context).toSpanned() + /** Returns value as string depending on the [Type] of element. */ fun Type.getValueAsString(context: Context): String = getValueString(this) ?: context.getString(R.string.not_answered) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/OptionSelectDialogFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/OptionSelectDialogFragment.kt index 4fb0238ca0..ab40dc3eb1 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/OptionSelectDialogFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/OptionSelectDialogFragment.kt @@ -41,6 +41,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage import com.google.android.fhir.datacapture.extensions.optionExclusive +import com.google.android.fhir.datacapture.extensions.toSpanned import com.google.android.fhir.datacapture.views.factories.OptionSelectOption import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemDialogSelectViewModel import com.google.android.fhir.datacapture.views.factories.SelectedOptions @@ -208,7 +209,7 @@ private class OptionSelectAdapter(val multiSelectEnabled: Boolean) : } else { (holder as OptionSelectViewHolder.OptionSingle).radioButton } - compoundButton.text = item.option.displayString + compoundButton.text = item.option.displayString.toSpanned() compoundButton.setCompoundDrawablesRelative( item.option.item.itemAnswerOptionImage(compoundButton.context), null, diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactory.kt index 6e41ca6b9f..179eb7da79 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactory.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.extensions.ChoiceOrientationTypes import com.google.android.fhir.datacapture.extensions.choiceOrientation -import com.google.android.fhir.datacapture.extensions.displayString +import com.google.android.fhir.datacapture.extensions.displayStringSpanned import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage import com.google.android.fhir.datacapture.extensions.optionExclusive import com.google.android.fhir.datacapture.extensions.tryUnwrapContext @@ -104,7 +104,7 @@ internal object CheckBoxGroupViewHolderFactory : val checkbox = checkboxLayout.findViewById(R.id.check_box).apply { id = viewId - text = answerOption.value.displayString(header.context) + text = answerOption.value.displayStringSpanned(header.context) setCompoundDrawablesRelative( answerOption.itemAnswerOptionImage(checkboxGroup.context), null, diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DialogSelectViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DialogSelectViewHolderFactory.kt index 85769b3679..206a4b77e2 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DialogSelectViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DialogSelectViewHolderFactory.kt @@ -32,6 +32,7 @@ import com.google.android.fhir.datacapture.extensions.getRequiredOrOptionalText import com.google.android.fhir.datacapture.extensions.getValidationErrorMessage import com.google.android.fhir.datacapture.extensions.itemControl import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned +import com.google.android.fhir.datacapture.extensions.toSpanned import com.google.android.fhir.datacapture.extensions.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.views.HeaderView @@ -76,12 +77,12 @@ internal object QuestionnaireItemDialogSelectViewHolderFactory : val questionnaireItem = questionnaireViewItem.questionnaireItem val selectedOptions = questionnaireViewItem.extractInitialOptions(holder.header.context) - holder.summary.text = selectedOptions.selectedSummary + holder.summary.text = selectedOptions.selectedSummary.toSpanned() selectedOptionsJob = activity.lifecycleScope.launch { // Listen for changes to selected options to update summary + FHIR data model viewModel.getSelectedOptionsFlow(questionnaireItem.linkId).collect { selectedOptions -> - holder.summary.text = selectedOptions.selectedSummary + holder.summary.text = selectedOptions.selectedSummary.toSpanned() updateAnswers(selectedOptions) } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DropDownViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DropDownViewHolderFactory.kt index 1a3c581f1c..8bcfc54e31 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DropDownViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DropDownViewHolderFactory.kt @@ -18,6 +18,7 @@ package com.google.android.fhir.datacapture.views.factories import android.content.Context import android.graphics.drawable.Drawable +import android.text.Spanned import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -33,6 +34,7 @@ import com.google.android.fhir.datacapture.extensions.getValidationErrorMessage import com.google.android.fhir.datacapture.extensions.identifierString import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned +import com.google.android.fhir.datacapture.extensions.toSpanned import com.google.android.fhir.datacapture.extensions.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.views.HeaderView @@ -92,8 +94,8 @@ internal object DropDownViewHolderFactory : answerOptionList .firstOrNull { it.answerId == selectedAnswerIdentifier } ?.let { - autoCompleteTextView.setText(it.answerOptionString) - autoCompleteTextView.setSelection(it.answerOptionString.length) + autoCompleteTextView.setText(it.answerOptionStringSpanned()) + autoCompleteTextView.setSelection(it.answerOptionStringSpanned().length) autoCompleteTextView.setCompoundDrawablesRelative( it.answerOptionImage, null, @@ -105,7 +107,7 @@ internal object DropDownViewHolderFactory : autoCompleteTextView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> val selectedItem = adapter.getItem(position) - autoCompleteTextView.setText(selectedItem?.answerOptionString, false) + autoCompleteTextView.setText(selectedItem?.answerOptionStringSpanned(), false) autoCompleteTextView.setCompoundDrawablesRelative( adapter.getItem(position)?.answerOptionImage, null, @@ -165,7 +167,7 @@ internal class AnswerOptionDropDownArrayAdapter( val answerOption: DropDownAnswerOption? = getItem(position) val answerOptionTextView = listItemView?.findViewById(R.id.answer_option_textview) as TextView - answerOptionTextView.text = answerOption?.answerOptionString + answerOptionTextView.text = answerOption?.answerOptionStringSpanned() answerOptionTextView.setCompoundDrawablesRelative( answerOption?.answerOptionImage, null, @@ -187,4 +189,6 @@ internal data class DropDownAnswerOption( override fun toString(): String { return this.answerOptionString } + + fun answerOptionStringSpanned(): Spanned = answerOptionString.toSpanned() } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactory.kt index 876a4f3ac6..f912560425 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactory.kt @@ -28,7 +28,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.extensions.ChoiceOrientationTypes import com.google.android.fhir.datacapture.extensions.choiceOrientation -import com.google.android.fhir.datacapture.extensions.displayString +import com.google.android.fhir.datacapture.extensions.displayStringSpanned import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage import com.google.android.fhir.datacapture.extensions.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid @@ -113,7 +113,7 @@ internal object RadioGroupViewHolderFactory : val radioButton = radioButtonItem.findViewById(R.id.radio_button).apply { id = viewId - text = answerOption.value.displayString(header.context) + text = answerOption.value.displayStringSpanned(header.context) setCompoundDrawablesRelative( answerOption.itemAnswerOptionImage(radioGroup.context), null, diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactory.kt index a6621b2aba..cb69258760 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactory.kt @@ -26,6 +26,7 @@ import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility import com.google.android.fhir.datacapture.extensions.getLocalizedInstructionsSpanned import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned import com.google.android.fhir.datacapture.extensions.localizedPrefixSpanned +import com.google.android.fhir.datacapture.extensions.toSpanned import com.google.android.fhir.datacapture.extensions.updateTextAndVisibility import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.views.QuestionnaireViewItem @@ -90,7 +91,7 @@ internal object ReviewViewHolderFactory : QuestionnaireItemViewHolderFactory(R.l answerView.visibility = GONE } else -> { - answerView.text = questionnaireViewItem.answerString(answerView.context) + answerView.text = questionnaireViewItem.answerString(answerView.context).toSpanned() answerView.visibility = VISIBLE if (questionnaireViewItem.validationResult is Invalid) { errorView.findViewById(R.id.error_text_view).text = diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponsesTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponsesTest.kt index 165d95fe45..236ed8796a 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponsesTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireResponsesTest.kt @@ -260,6 +260,121 @@ class MoreQuestionnaireResponsesTest { assertResourceEquals(questionnaireResponse, packedQuestionnaireResponse) } + @Test + fun `should pack repeated groups recursively`() { + val questionnaire = + Questionnaire().apply { + addItem( + QuestionnaireItemComponent().apply { + linkId = "repeated-group" + type = Questionnaire.QuestionnaireItemType.GROUP + repeats = true + addItem( + QuestionnaireItemComponent().apply { + linkId = "nested-repeated-group" + type = Questionnaire.QuestionnaireItemType.GROUP + repeats = true + addItem( + QuestionnaireItemComponent().apply { + linkId = "nested-nested-question" + type = Questionnaire.QuestionnaireItemType.BOOLEAN + }, + ) + }, + ) + }, + ) + } + + val questionnaireResponse = + QuestionnaireResponse().apply { + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "repeated-group" + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-repeated-group" + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-nested-question" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + value = BooleanType(true) + }, + ) + }, + ) + }, + ) + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-repeated-group" + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-nested-question" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + value = BooleanType(false) + }, + ) + }, + ) + }, + ) + }, + ) + } + + val packedQuestionnaireResponse = + QuestionnaireResponse().apply { + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "repeated-group" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-repeated-group" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-nested-question" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + value = BooleanType(true) + }, + ) + }, + ) + }, + ) + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + addItem( + QuestionnaireResponseItemComponent().apply { + linkId = "nested-nested-question" + addAnswer( + QuestionnaireResponseItemAnswerComponent().apply { + value = BooleanType(false) + }, + ) + }, + ) + }, + ) + }, + ) + }, + ) + }, + ) + } + + questionnaireResponse.packRepeatedGroups(questionnaire) + assertResourceEquals(questionnaireResponse, packedQuestionnaireResponse) + } + @Test fun `should not modify non-repeated groups while packing repeated groups`() { val questionnaire = diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactoryTest.kt index bc49267601..f5f361cc87 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/CheckBoxGroupViewHolderFactoryTest.kt @@ -124,7 +124,8 @@ class CheckBoxGroupViewHolderFactoryTest { val checkBoxGroup = viewHolder.itemView.findViewById(R.id.checkbox_group) val children = checkBoxGroup.children.asIterable().filterIsInstance() children.forEachIndexed { index, view -> - assertThat(view.text).isEqualTo(questionnaire.answerOption[index].valueCoding.display) + assertThat(view.text.toString()) + .isEqualTo(questionnaire.answerOption[index].valueCoding.display) assertThat(view.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) } } @@ -161,7 +162,8 @@ class CheckBoxGroupViewHolderFactoryTest { val checkBoxGroup = viewHolder.itemView.findViewById(R.id.checkbox_group) val children = checkBoxGroup.children.asIterable().filterIsInstance() children.forEachIndexed { index, view -> - assertThat(view.text).isEqualTo(questionnaire.answerOption[index].valueCoding.display) + assertThat(view.text.toString()) + .isEqualTo(questionnaire.answerOption[index].valueCoding.display) assertThat(view.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactoryTest.kt index cb2d536d58..6f4bc8a3e4 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/RadioGroupViewHolderFactoryTest.kt @@ -118,7 +118,8 @@ class RadioGroupViewHolderFactoryTest { val radioGroup = viewHolder.itemView.findViewById(R.id.radio_group) val children = radioGroup.children.asIterable().filterIsInstance() children.forEachIndexed { index, view -> - assertThat(view.text).isEqualTo(questionnaire.answerOption[index].valueCoding.display) + assertThat(view.text.toString()) + .isEqualTo(questionnaire.answerOption[index].valueCoding.display) assertThat(view.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) } } @@ -154,7 +155,8 @@ class RadioGroupViewHolderFactoryTest { val radioGroup = viewHolder.itemView.findViewById(R.id.radio_group) val children = radioGroup.children.asIterable().filterIsInstance() children.forEachIndexed { index, view -> - assertThat(view.text).isEqualTo(questionnaire.answerOption[index].valueCoding.display) + assertThat(view.text.toString()) + .isEqualTo(questionnaire.answerOption[index].valueCoding.display) assertThat(view.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactoryTest.kt index cfa8ea2408..3e43a24d3d 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/factories/ReviewViewHolderFactoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * Copyright 2023-2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -180,7 +180,7 @@ class ReviewViewHolderFactoryTest { ), ) - assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text) + assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text.toString()) .isEqualTo( ApplicationProvider.getApplicationContext().getString(R.string.not_answered), ) @@ -206,7 +206,7 @@ class ReviewViewHolderFactoryTest { ), ) - assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text) + assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text.toString()) .isEqualTo(ApplicationProvider.getApplicationContext().getString(R.string.yes)) } @@ -321,7 +321,7 @@ class ReviewViewHolderFactoryTest { ), ) - assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text) + assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text.toString()) .isEqualTo("Yes") } @@ -340,7 +340,7 @@ class ReviewViewHolderFactoryTest { ), ) - assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text) + assertThat(viewHolder.itemView.findViewById(R.id.answer_text_view).text.toString()) .isEqualTo("Not Answered") }