From 2e57f26f73dda7fc4a574c04b24b943293d1162d Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Fri, 1 Oct 2021 19:06:36 +0300 Subject: [PATCH 01/75] Add itemImage extension support on text and group questions - Add example usage in reference app - Add AttachmentResolver in DatacaptureConfig --- .../fhir/datacapture/DataCaptureConfig.kt | 11 ++++++ .../MoreQuestionnaireItemComponents.kt | 34 ++++++++++++++++++- ...stionnaireItemEditTextViewHolderFactory.kt | 17 ++++++++++ ...QuestionnaireItemGroupViewHolderFactory.kt | 16 +++++++++ .../questionnaire_item_edit_text_view.xml | 9 +++++ .../questionnaire_item_group_header_view.xml | 10 ++++++ .../new-patient-registration-paginated.json | 16 +++++++++ 7 files changed, 112 insertions(+), 1 deletion(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt index b48e836f53..111f833509 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt @@ -17,6 +17,8 @@ package com.google.android.fhir.datacapture import android.app.Application +import android.graphics.Bitmap +import org.hl7.fhir.r4.model.Binary import org.hl7.fhir.r4.model.Coding /** @@ -30,6 +32,8 @@ object DataCaptureConfig { * `choice` and `open-choice` type questions. */ var valueSetResolverExternal: ExternalAnswerValueSetResolver? = null + + var attachmentResolver: AttachmentResolver? = null } /** @@ -44,3 +48,10 @@ object DataCaptureConfig { interface ExternalAnswerValueSetResolver { suspend fun resolve(uri: String): List } + +interface AttachmentResolver { + + suspend fun resolveBinaryResource(uri: String): Binary? + + suspend fun resolveImageUrl(uri: String): Bitmap? +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 3df72b6916..ea77ed2529 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -16,12 +16,15 @@ package com.google.android.fhir.datacapture -import java.util.Locale +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType +import java.util.Locale internal enum class ItemControlTypes( val extensionCode: String, @@ -39,6 +42,8 @@ internal const val EXTENSION_ITEM_CONTROL_URL = internal const val EXTENSION_ITEM_CONTROL_SYSTEM = "http://hl7.org/fhir/questionnaire-item-control" internal const val EXTENSION_HIDDEN_URL = "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden" +internal const val EXTENSION_ITEM_IMAGE = + "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage" // Item control code, or null internal val Questionnaire.QuestionnaireItemComponent.itemControl: ItemControlTypes? @@ -168,3 +173,30 @@ fun QuestionnaireResponse.QuestionnaireResponseItemComponent.addNestedItemsToAns */ private inline fun Questionnaire.QuestionnaireItemComponent.getNestedQuestionnaireResponseItems() = item.map { it.createQuestionnaireResponseItem() } + +internal val Questionnaire.QuestionnaireItemComponent.itemImage: Attachment? + get() { + val extension = this.extension.singleOrNull { it.url == EXTENSION_ITEM_IMAGE } + return if (extension != null) extension.value as Attachment else null + } + +val Attachment.isImage: Boolean + get() = this.hasContentType() && contentType.startsWith("image") + +suspend fun Attachment.fetchBitmap(): Bitmap? { + if (data != null) { + return BitmapFactory.decodeByteArray(data, 0, data.size) + } else if (url != null) { + if (url.contains("/Binary/")) { + return DataCaptureConfig.attachmentResolver?.run { + resolveBinaryResource(url)?.run { + BitmapFactory.decodeByteArray(this.data, 0, this.data.size) + } + } + } else if (url.startsWith("https") || url.startsWith("http")) { + return DataCaptureConfig.attachmentResolver?.run { this.resolveImageUrl(url) } + } + } + + return null +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index aaf6e0a78a..6b7a288e1a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -18,14 +18,20 @@ package com.google.android.fhir.datacapture.views import android.text.Editable import android.view.View +import android.widget.ImageView import android.widget.TextView import androidx.core.widget.doAfterTextChanged import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.fetchBitmap +import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.localizedPrefix import com.google.android.fhir.datacapture.localizedText import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import com.google.android.material.textfield.TextInputEditText +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.hl7.fhir.r4.model.QuestionnaireResponse internal abstract class QuestionnaireItemEditTextViewHolderFactory : @@ -41,12 +47,15 @@ internal abstract class QuestionnaireItemEditTextViewHolderDelegate( private lateinit var prefixTextView: TextView private lateinit var textQuestion: TextView private lateinit var textInputEditText: TextInputEditText + private lateinit var itemImageView: ImageView override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem override fun init(itemView: View) { prefixTextView = itemView.findViewById(R.id.prefix) textQuestion = itemView.findViewById(R.id.question) textInputEditText = itemView.findViewById(R.id.textInputEditText) + itemImageView = itemView.findViewById(R.id.itemImage) + textInputEditText.setRawInputType(rawInputType) textInputEditText.isSingleLine = isSingleLine textInputEditText.doAfterTextChanged { editable: Editable? -> @@ -64,6 +73,14 @@ internal abstract class QuestionnaireItemEditTextViewHolderDelegate( } textQuestion.text = questionnaireItemViewItem.questionnaireItem.localizedText textInputEditText.setText(getText(questionnaireItemViewItem.singleAnswerOrNull)) + + questionnaireItemViewItem.questionnaireItem.itemImage?.let { + GlobalScope.launch { + it.fetchBitmap()?.run { + GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + } + } + } } override fun displayValidationResult(validationResult: ValidationResult) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 06142d76ee..1e8e0fe45e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -17,24 +17,32 @@ package com.google.android.fhir.datacapture.views import android.view.View +import android.widget.ImageView import android.widget.TextView import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.fetchBitmap +import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.localizedPrefix import com.google.android.fhir.datacapture.localizedText import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch internal object QuestionnaireItemGroupViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_group_header_view) { override fun getQuestionnaireItemViewHolderDelegate() = object : QuestionnaireItemViewHolderDelegate { private lateinit var prefixTextView: TextView + private lateinit var itemImageView: ImageView private lateinit var groupHeader: TextView override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem override fun init(itemView: View) { prefixTextView = itemView.findViewById(R.id.prefix) groupHeader = itemView.findViewById(R.id.group_header) + itemImageView = itemView.findViewById(R.id.itemImage) } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { @@ -51,6 +59,14 @@ internal object QuestionnaireItemGroupViewHolderFactory : } else { View.VISIBLE } + + questionnaireItemViewItem.questionnaireItem.itemImage?.let { + GlobalScope.launch { + it.fetchBitmap()?.run { + GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + } + } + } } override fun displayValidationResult(validationResult: ValidationResult) { diff --git a/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml b/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml index eb126996a5..62252857b0 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml @@ -46,6 +46,15 @@ + + + + + diff --git a/reference/src/main/assets/new-patient-registration-paginated.json b/reference/src/main/assets/new-patient-registration-paginated.json index 046f2e08bf..6ae9d9ba60 100644 --- a/reference/src/main/assets/new-patient-registration-paginated.json +++ b/reference/src/main/assets/new-patient-registration-paginated.json @@ -64,6 +64,14 @@ "expression": "Patient.name.given", "name": "patientName" } + }, + { + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { + "id": "okImage", + "contentType": "image/png", + "data": "iVBORw0KGgoAAAANSUhEUgAAAJcAAACbCAYAAABvXQkCAAABQWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSCwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAziDFwMVgwSCTmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsisGxkXF99J/hlfbFL18ZPTxwhM9SiAKyW1OBlI/wHi5OSCohIGBsYEIFu5vKQAxG4BskWKgI4CsmeA2OkQ9hoQOwnCPgBWExLkDGRfAbIFkjMSU4DsJ0C2ThKSeDoSG2ovCHAaG/n6mBmE+BJwK8mgJLWiBEQ75xdUFmWmZ5QoOAJDKFXBMy9ZT0fByMDIgIEBFN4Q1Z9vgMORUYwDIVYI9J+VJwMDUy5CLCGAgWHHB5A3EWKqOgwMPMcZGA7EFiQWJcIdwPiNpTjN2AjC5t7OwMA67f//z+EMDOyaDAx/r////3v7//9/lzEwMN8C6v0GAN6SYHzofPxzAAAChmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTU1PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE1MTwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+MjwvdGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KLFQJjwAAMuxJREFUeAHt3QeYLEXVBuBGQDD9Yo7oRTAgElQMiOgVDJgVUMSIEZAgYCIKIqI+oiISDYgIKmYUzOhVUQTMOYM5AYoKylXZv9+Sby2G3Z25OzN3A3326e3uiqfO+erUqerqnlWuuOKKiaajTgJjkMC1xlBmV2QngSKBDlwdEMYmgQ5cYxNtV3AHrg4DY5NAB66xibYruANXh4GxSaAD19hE2xW82kIXwSqrrDLZhMsvv7z5z3/+01zrWtdq1lxzzcnw+mJiYqKkSdiqq67a1GUkXLp2DbDcildmTeKkcYiTJuUkrzSrr756yfbvf/+7lCeNOlOetOLwjVZbbbVylJv2n/iFSqss9EXUv//970X2lLLGGmtMKk1ggEbJrimKYqUNAWSAQeHia6qVm+uASLr6WrwjwBNfA8l9TVOVl/iAMeBM+EI6L3hwRbnp+QGHe8AJYHqVRHkOFg4YcihvJkBMpdypABW+AhL89AIcyP/yl7+UTqFjIOnlzX0AOFW98z1swYMroKCE2QAjCgrY3ANXwPjjH/+4KFx8DeAMX3e6050KMOt4cckvvLaUqS9A642r65C2A1ckNgfnv/71r811r3vdSWUC2z//+c+icFZL3L/+9a8SRlEsQqwCdr/1rW81f/7zn5tf/OIXzY9+9KMGmFz/8Y9/bAy5N77xjQuolFErHniA8OKLL26ud73rNTe72c2a293uds16661XDtc3utGNmk033bSkiyXDn+vc4yEdpAYaUP7jH/9orn/960uyIGnBW65aSTQAAMLiMEeZgAWIwPOFL3yh+fSnP12ABSTiHMkXywM8wpWhXGeUodZZHkBYvnx5OWoLJi1w3+Uud2nuf//7N/e9730blg4Qe0GTyci1r33tq1g69S9UWhTginKBIVaJUijsJz/5SfPtb3+7OfPMM5uvfvWrxSJJR7kU/7e//W0SXPLUB6X2U654AJvpYPWAT1qW8K53vWtz73vfuwDtIQ95SOFD/lDKHKT+5JmP50UBLsqLNSHkH/7wh80nPvGJ5uyzz26+8Y1vlOElzj0HnmWibGGUmryxRMqgYAdKuDNiwcTFos2UH/BZR2nUachWp6HUAWwPeMADmsc97nHNPe5xjxKnDvnwGR6ELTSac3BR8HRrUhFwhBoLxeL0Ev/ky1/+cvOud72rDHnKvdWtbtX87ne/K0qi4CgZKJRlqDMMoRpM7mOJLrvssgIMiq7BJb9y5E/enOUPARBe0gFYVmUHaOIMk2Rwk5vcpNl2222b5z73ueXeMK6OtdZaqxTnWt4M0albGEq70iZhcwnOOQcXgRimEAVceuml5ZqwCcmwdcMb3nAyHkAQpVDUb3/72+ajH/1o8/73v7/56U9/WgAgjbKkuc51rjOpEFZDGGUAqOPXv/51Kf/Wt751c/vb374crlkUeVmT2jKpG89R8M9+9rNiGS+66KLCywUXXFAmBPi65JJLinIDTDw5Miwbml3rAMLdO9/gBjdonvjEJzY77bRTg5eatCGd0UREW5WvHOcADX8mJP/3f/9XZ1+p13MOLsLQu2qh1BaFNAg8oCJQszAKefe7311AdeGFFxZnnXABQnlAFIURfITtzKLd/e53L442S0FZFEq5qSdaAHa8KSM8Kl85zixTTSyaPDoFHkwcvv/97zdf//rXm9/85jclj7KAhPLlB3JhrBmegRmA3eNvzz33bO585ztPykF4ZrJ13bkOb8qcS5oX4CJowuwd7ghJOIVTVJz1448/vnFQgqUAaRzImdIJFtiU7bxkyZLmYQ97WPP4xz++ONTSZmgSz0o616SsDIV1eH2troCuDgcSQAP2EMv62c9+tlm2bFnxCy2gahugqAvQ8GGIxzeytPHd7363ecITntC84hWvaG5xi1sU8JIV/iOT1KHeLGHgHX9zRfMCXL09jVIICREi3wOdc845zRFHHFFmgJRiSLrpTW9a0lAKhfCRxPFfWLitt9662WyzzcpSQCmk/UeplN6rGPEBdJSiLNQLoMQHBBTpSPqSqf0HNNLIXwPtBz/4QbFofERD8x/+8IdSt/zSao/0Zrr3uc99mt///vdFJvvvv38ZLpWPVxZSHnIKjzpieAmf4WdlnuccXAQRH0LDKYJgCQcZ/oDtLW95S3PssccWCxY/ArD+9Kc/ld6ptwLkbW9728b0noUy3TfUIdYBqYuFUq+6lAUADsTipW73UU4UJwz1hlM0S+IsrTIcylWnMMAHBteADxQ6DqB98pOfLId1OGUAFl50EsspLJZrC7zWyvbZZ5/mkY985H+ZufK/8uVJpzE8944GV8kw5ps5BxcBBAAUVitRbzWMvPrVry4C5idR2M9//vPJIQdoKGrdddct1ulRj3pU8aciNyCixBowiavPFBqQBTjiAb2+r/MERHXYVNfKTSeq2wcMtfJ1IpOTo446qizwmsjoNCYXAabydaI73vGOzWMe85jmmc98ZulQwtUB4PxH5Frb54rmHFwEr7exIo5Mu88777zirL/nPe8pCuZbGc4I2tQdGADTYxZWaocddpgUsjJjRQjXQanCKAtYMiwqB/BqpVNGgNbrh/UqSpnya0PKUCagOMdq9ebLvTZnuGTFYpXPOOOM5rjjjivrdMrhjylLeuASBnCGTEsX22yzTSkS3+lIix5cBM66xDq51ng9VuMJg9AIi4LQiSee2Bx55JElH6vkmR9inQjX0OCalTr00ENL3GL9Z+Jy+umnN1/72tdKE4GdrJxZVTIESAB7ylOeUmQJiOQO4IZhMkQ6I1kDp4OFm84qlwxD/hu75bLWE2ukIRpMMOnl+GdN9F7AedOb3tScdNJJZY0IAAFr7bXXLnkMkyyXmdPTn/70MvyNUzhDynYk2cnJmtlb3/rW5tRTTy2yIr90UkDTQcnF2tguu+zS3PKWt5zs0JF35KwDAyUCvHT6EjDif2MHV3oZYbjOgmgA9ctf/rIMbYa9l7/85cWpjbMNmJxXa1t62oYbbtg8+clPLo9KlAOMcV5HLJd5UxxwxTczczzooIOaZe1SBueeFTLL1DEBjDw8HJfmXve61yTAWCzWDAVQyiXnAG0sDW6Zmhjn0VqWibb3TLT+0kQLLrcTLVAmWuCUa//abS8T7bO1ibZHTtzmNreZaIe8iXZ2NHG3u91tou2R5bzvvvtOtDOpyTztcFCux8n7fCi7BcBkm12Q3ete97oip3ZEmGh3XEy0QJtoH4ZPrL/++kWG7Sx54rTTTptowVbyth17IvIS0Pp2E8qll3G2ceyWS2/SO+JP6YV6TZzYL33pS8VinXvuuWXNihlv21/iOfBM/HOe85zmGc94RvERWDD5DQ1I2sVM2koOLHRkpr0mPG2HK1uI4kfFh7LoakWfBTNUkj25kVUslZGjBdZVZqujluPYwaUBAZYGGhoJA3l8Y9XZkMlnEM5ZB0h+llmhZYi2RxbfgEBQhMwHs/6zmMkjI2t3yPNLwyHKzPJ5z3ve5OMlQDR7NtxFprvvvnvz7Gc/e9KRl7denhhn51y1RffBKhwXZVqssRodB/IjH/lIWW3nVyF+FX9AeodHNRZOCYuVEpZlBdYNZT2n3CzSf3xLciEDfpNrwDJJAqatttqqPKmw2CxeB9Q5WTrXrJiJ0QYbbFDCxOnksWDjFNvYLVcawoIFaB/84AfLUoOGEwJrRlgB11577dU4CC+kh7l3Bi7XBD7Onpe65/Ic+eEh1ir8xNF37zHSgQceWIZQTy6AiLx12taPbZ7//Oc3O++88+QoQobknZl8yhzl+aov482iZDsSKNpBEAiQzFCc00NMp5EZzwEHHFCelQEVf2KdddYpMyJpX/aylxVBlMTtP+AJgHIGKkNt7pN2MZ5Z68ggPlXu4x4YLrfffvvm7W9/e1n/s/+fxTMqcDecjz766LKUAXRGETJk0YCMHoRHj86A7DwUtQAYasbQMta29b/UAiqXU55bgE3c8573nGhNdJnhtFtfJtqpM498ol1ymHjnO9850QpqMq8Z5rD8Lfb8hKWNoXaZYmKLLbYos8Z26Wai7bgTrc820VqzMrNs3ZGSlGx7Kbp0bkFXooeR39A+V4Y6K8Wu47zrDe75WXoJ8ojiV7/6VekVnHc90fqWGeELX/jC8pwsvVF6PkOcf/cdXV0CrfKLnMmKlVvSbi0yGfL81QNvQ6ShkT9mTfH8888vEyTpoquUqixD7ahGhqGHRYwBEoYyBBoeNRbg+FSYfsELXlBekGCG73CHO5RGGDo1kK9g1iOOIEJ5bJH77nx1CZA9OQMWeaIHP/jBZRbu4bbOSzeOjTbaqPnmN79ZlijMQg2FrXkq+cieLnTugI4Oh6GhwcUyxUphBJAwbFYYsL3xjW9sPvzhD5fx3zhum4wGEMp+++3XPOIRjyht4GAGUPyCjvpLgIz5rcDDOgEY+T/wgQ9sDj/88PLckfW3Z0y4Z7LWFD2zBB76kDf+coBFpwA7DA0NLgxgDmFebwiohL3vfe8rzws1nPNv5qLXAJ/nYE972tMK6KQNsFyLV1ZH/SXA2ujUyEgRuW2++ebFSgEXHbFiLBTZfuhDH2pObDcIJI8y6C80CnCNxOfKrAJDGNQQZ28z86XQBe2LC8Z5s8aNN964mOg3vOENJW3yS8diBZx6U4ArrqOrSwBYgIfFMisELvfkz8WwYdLs0T1weRZpJslqGSJvfvObl3cJ+Mf8rVHKfmjLBVDpNYCgYcgWZBvfTGkJIOspLJ1G2BCnQYBFKPG1YrE0vnbury7WLoQEyJN8TY4ABdEJuVrNd33IIYeUzgyA9KDTApElDMMjRx9xU0LyDUtDg0vDQnoIAh7junUXj3AAR2PTM4455phimqXVo+xH0mjXjrr3SdPR9BIgr4wUroFNp40sXQOTUcInBQBKB6YPcr6gHVHaB+GlAhYNGVZj/UrALP8NDS4MYlQD9Jx8wMOjG+g3JdarMMvCHXzwwcXHSqNnyXeXbUAJ0A3AmSx5uYOOGAQdne7oyEbEE044oYAS+OiKY58RacCqrpZsaHApkZ+kAUgvshr8ve99rwyJJbD9h1EvFNjkx5p1tPIkQD86uC/uABhXxbBIZ/TipZATW+eexTKKZEjMebacDg2uPIxmuZBFOm/p2K3ANHuir9e499hHAzQUBZDlpvs3FgmwUvWkyPYl36YIsWwslR0mRpUADrCMLsPQcLnbmoELg2YiyHuFWXzzXMs1MLFY3nTOM6vaeSwZu39jkYBODzyZMAEN62Xl3kSK7lwDoIff3g4HMEagBuVsmBsaXMwoC8Uyvfe9721s/vNA1JiOSUsPS5cubXbdddey2JcZDcY7gM1GZSuWx4y7XpxmDCwFPfWpT73K8BeDcNhhh5UKrEsOS0ODCwOskcMsEAEOwEG/Rz1e+0IAFxI/rNlNWd15ZgnEd2KpdHxAsrPX8MhxF87CAdTnPve55jOf+UwpcM4deubWFJZJNdPIKjwgYe6hD31oeRQBfL7YwlpprAYOa3ZnFmkXSwIeDVnmMWvPUgNrxmV50pOeVHRAF8KkBb6TTz65CK82BrORZl/LZUzO2Fxfq8w9xs1G7CzNMy4AY5kMmfZxAxnmhZml6C3WWkZB7U6yZpX2JyPLuf3/n8uXT17/9eJ2fa2Nu2J5u++++lt+Wbt/PGFtGwwbAB//UbuyT21YHpXFqdapXKO4A+576xVPqSxJ0gubLQEUudsz51wf9td7S4hudH6+F/rYxz5W1inpCg8OOjQS5T6ymomvvuCSuW5kQKHwxHlOlc8DARuBceK9CW3oc9RllIwj+vePFtgXtg/CQ6u1azd/b808umHbO5e3fK7aCqlQy9elrRAprpj89h5pU6yoODQKn0M5thjxPa0pIXJQF0Uh1xxu90AlXt0UW3gsqcb3j8uiTobAoyEjkaUi/jMeEEDSIRAifA2iz4HARQBRSJSQ3qcyL1pgDAMUxRLYU+QNYOlZKnG9hOlh6TptfTfNSxptoy9vFXT9K1ea/90C69qt4lq0N0BIeddrhSh+9TbcME5QOgMCgrSLIEehXHJAys3TDPIAKNYC6ah4MCQhw5RHNVPJrCQY4T9vrbcbOAuA8KhOcrCLxfIEinxSrY4gTT/673aGPqkIgmDqxqZwq7umr+KTjnB8w8BbO70Aqu9dD9IDZmLvX21DKStDT4D1x/aFhbPOOqvwRlE6h6UQC4kOILtJu1q9/F/LiyL1XOWkd+JLvlFYMC9PqBsZbn3VRpgH+77Z5cVfL/xusskm5TrAJkcyHSdp54477lg+MGe0UTfjoOPxvV70ohcVA0F+dB6rBQvAGBxMxeNAL2hgAHoVmGuKQHY9fOADHyjKY6XEO97xjndcZbFO2hpY7kdBfKmaftU+K8v3FfiKKIIhPNbBB9X4G49ph23ZCUlvBDAU0KfNJXCW/yiDrJTla9I+rPLFL36xWC1ysj7oc5n8VWD25Rqvg7UvBJcaxyGzuinajb/HPvax5U0hOo5V5ad9/vOfLwCK0y+vjkzXfO2ZOl/fLTcRdICFGdcO/oQFOcxJF1RvueWWzW677VbupZuKAsKp4lYk7JKLLm7WbAFzRduzvt5aUQ/LKdDmOGtqBAVceCQQwzdLK/7C9kH7Zu2eJ3HhHb9JPx3vK8JfyuA67L333uWjwNb+WAbWjDwpi7XAA5/HR+6ADsBmsgwrwsd0adXJH8SHZQgbOZe0W6PwAzxGIOCXzkFW0Z088SWnKn9qzVcpIxwFo6DW9bJly4oQhCGV8rl89lq+5CmR1T/pRkVrXfkFF1t8PN0HLr6VffmIX0NhlAU0puAsmM+J+7jHiVdumDMcpB1p8yh4pCQz6Te/+c3lhV+LzdwFwGdZ85xVnXjDr8+ce/0uW2FGwcd0ZeThta3RQMSJ5wMCNYPhQ8bkVoMoctJZZ6K+4JLZsKECFGDEzBOIOOFQzUya3vYSRh0jp1Yp/Cv+geEGH4CFF1YKkFgIe5vwSdmIYAjJV3V6lZg4IB2W1A/wrJEXfA0vLJdwnS+zSdfCgdwvbnCm+Tsrg8iBzPh9ngXjg5GgYx/fY8EQmaKsFIwEXHoVRDODwANszKdPLapQOOC49g3SOK8qr0EVYCZsVGDjHOth4YEC+TDWeAAEqMSlA+iV2kR4HOxTTjmlCM0QSsny4z1T8RI5wz/lTndYXPZDC2RCKRSVaT8g4VE4X1AHIEv1O9tZYps4PpE60g7W2P2wxM+LVTJsW1LCUxZUyc8IhegXb4CH+ulvIMtF4GmYQgk9v0xBCIQkTLoHPehBk1ZO2nGT2SJHOQAPIHSCXM/Eg2HIrM0+NEqmyPg5/XrmTOUSvLJ8KlwdAA7QZIUoahDL6IMjLIv8SBkoCi43Q/yLjLQZ6P10DL60nc7JxOw2HTQGQ5XhZbrqBwJXxliVpZGsloodFAlYeh5wJf10lY4yXAPNaCgSf6wPnghtED4o2V5ylhgpjwC1ZxBSZy/Jix+HH7HCi3KFqw9f+OzX85WrbVF00isnoOite0Xv05HwFf2xZupERim/TMJSao/0rBdKmnIzxb+BwKVQhIGAS4XuCVeFnNP4Wv0qnYKPWQdZfffyJ14o0Fnj8RxfYabCvXFkaJTe8CA/ItTZEMUHXM7kwnlXNtk5lG0YzHA0Uz2eCZInGQdcUa5ONApKuQC7dOnSyXp0AnV/5zvfKT5gOlJkk/vpeBgIXClEA10blznBeh8BYYIp9WUajEqHCHdlEFDhK8eK1ClP8q9IvkHTUo7yycVR1xelzlRWOmryS6u8UVHdGZRp+cMMlk51BDo20/b50BWtdyAuAxaV69n8AD3dtQOSPfRkuZjPlUqtwnzoLMLHF6uAL8N1P+IPsSx6LWGmQ9Rt7ldGHU8BDvU7W3pgGVkbYcqlMPexQHX+3mvLFoiVChjjtw1i+XrL670nL3wCEyILn1siB/UBtyM6l2bQIXkgcMU6KRjxIwiIMoJwr4pbljDMhFFMrwzi51EaqwDoGu+cXt+PBz8eBQTyE2g6TpQ5U/6p0gRcwOT7WcpzIEojTxMQ9fUjP9igDkctz5TXL/8g8eEDb2TmWWPaQI4mD1/5ylcKwGPNBil3YO0rFGmkFe4ISOWYsjbjjFE9UrowPQgjs07T1ufFT72ZQOKH8Lfw1o9Mu/14gt0cBAsQaWs6Sb8yporXdmU9/OEPL1N7ZZNPQOU6Fmiq/AnzAd2UVYNL+0YBsJSpjrTXBk/heCZPhoRfi3dH8oTH6c4DgQtYCAJgNMr6CyI8i4HCli5dWpggNMMRZgcx+9MxNnB42/lvfutbNU9/1jObVVZfrbnokr80N7jRWs26d75Tc8ml7Q82Xavdr/WPy5pLL2+foa3R/njUmms0ly2/vMQtv+I/5WHxju2DW2RIx3+GrUGHRnKZ7vDNBs8LKYlMrHcZJgE/HZBlSCdl/cmNbG3mY5Wlw0tdh2WDQZVcGjfNv5RRg2m77bYrPOhkeFYv4DEqyD059aOBwJVC9BTOHVKBilWi4YZDDAoPw5A/bsKTuu0Jf3b77U/K8/NzHgfZ+YofU2zK0Ams0LNWVqKdvTgiP8KvNiH5RkGGaOACMlN833xlJT0UjuICJoCyJEKRQPWSl7xkFCwMXAZ+HPQXfUanwj1NYGToPDqeqfCBtJ+CNNoskeADImEsVe+PTuqJBDsqJU3XCCvJBGGGA2CG6/ywp0corAKLxBoBkwfGFEjhwOgtZIRPgiNEbRoV38ryQRD1c5Y9HDa0KB/vZCeOwkwufKjFx1n8kAMrNio+ppOf8gMo51x7VOV9RvF4E+6rkH4HgBXWEfrRQOBSMGIlVIjClDCKzYPiEtn+C7hyP64zqxTy4BVgAN2DXxYMHxTIalAe5x0It2x3bsRiWUbRHukQC6Zd8ph5DkOx3vaQsZZmthag7eMKX+rSDluBWDjfKgNE/HI7xknajaJjZ8DxbS9+Vm25fMMWAdsgNBC4YrlUnAXLmEY9k9DinNZMDsLAsGlYAaRelhJg9C7f/GKxDOMe71Dg/e53vzLxwDMLh/gV4sJ/CWz/iY9jn7DZnAEUj0CmA+6xxx7FKtnyY5gGXtecaMCvyVAe5dfho7xO+dFb7m1g9ONWSBgM4BMFD+Vmhn99wZWClUFAhkU+VhxU8SxGKBUz9SuD9LI43kDDCrEI/BqHYdHUOmQoSnq+jjS51xZACNDkFTYM4SUWEX9kyLI6gF/d/ESWKoSv8JCwlXVOe4Edv9En8JENnskMZbicjre+Dn1dAEF4wEtAmEgcy+UaM8hQROkswriJIjRaneoDCHXjj/NuyDY781QBEQxls0wRUniUP/5PhJy42Z4zAZKfovCbsvHq0DmBSRyeAyxbhlY2kQFdsrL0GV6Bi8zwFCvXT799LZeKKAsplJnPfSqmpNrJi7+FOUPVOMnQgdSFrzRcmCEHX/yZOhx/iLK1DwEo/0Y67eLvmAAMS5GNMskiwHGPX7yoU4dNnDDxFnadx0m1XNSjPgfQk41rcpJOx8vWcWkjO9dTUV9wxSymkXpXvf5BQPwcFQunZIxBNaQn31SVjyIs5fcCXtl4D7iTTniGQdfJlzDpCHJU/o6ywoP6aj7Epd46LmF1WvHjoAA8PAI50Jip4iM8iNfhGBcUOc3EU19wEQAKimM2hVFeDulyiOto4UggAMIxfdJj9CouB90zHiFpZqK+4EoBwKVg1inmUBzkp/e7DxhVWjM9ExNd3PyRQIAVvdZGhT5HCq40WyWQ6wiSMYKJANB9Da7k7c4LRwIxEDEc0XUNsrSmn65ntmsppTt3EpiFBAYGV5AcSwW1UF37YEH5LPjosswTCcRCZaSiU1TrP6wmLve9577gUglSuNmDIz5WGAAwFMbKTfuvn9lMuu48fyQAMLVeo386p3uzyVDict97/l/K3pgr74NO4EIqSaHOAOQsXY4rs3anBSKB2gjUOo2e6V4auu8FVwzNVE3ta7nMDoBG4Q4Lk9avAiho9hA28cLNKFVqTayj+S8BuqIzlOEve85yb53LgvQ67VYhFH2Xm2n+9QVXLFbye3SSYVAFwGRxDSVt0J1z8nbn+S+BgMZjqFglZ8Ci3yy2akn0PV2r+oJLwRkaWTEPWJ0V7AC0/HJGmMGgRxisWkfzXwIBFE5dI3vhoj96tTLvKUy2INF79F0yTPGvL7gAKGOvApcsWXIVcInzOn0ozEF6RwtPAvTHmHhfErhcw0Ce0eb5Z0avmVrYF1wyx3K5ts8HoFSIxBmf7U1C/dBcEnX/5o0E6K+2XBijX5tC6VIcIEnHsPC3UfRfbqb5t0LgUhlwoRpExmf7q1EqrcfmEtH9m7cSyGiDQcDi3GfHsTguDlDZReteGv50P+u1QuBSoL1HKkglqRy48twpzGQGMm+l2jF2NQkADj3aFOoa2Y7E17L1GdEv6qffgcBVSrryn+01AVdMKiby8VrJBq28Lre7nh8SoFMWyY7T6BeIWK7sL4sRGYnlUnB2VNpA5wVYjh1EI+Dy0kGGQo6g2UVmFsOILUD2xk6uLX3kepiyF0Je27K1lSJ12rSb7F0PSwFKfGaO+6c+9ani3tAn3dqfhw+fa8AHvQJetm9Px8PAlivTUgX5hhMmTE01MGZUnMqlrdMLny2l0fUecw27plDeUNLmDFPOgNZvWBpERvGRbY6kR7txfXuN8bAITo/CGRR+NtdI/YMAuy+4okhACm2xxRbFarFoKgEoU1efO3SNoH4UyxG9INWw8KIu9S/mIzLX7owMAGEE6ZVN0q7IWVn0RIbZZerzn2Qc6yje+52ZxEXHsXrT1dcXXCpVSBqikd6mURHgOaDZjPHMM88sAhjlYx+N12jDYobDgKtf46Zr9EIKJ1dK1pHJnGL5Q7How7aFfmO9yNULGN5Wj1FhIV37ZkXCAi5YmIn6Prjuzaxgr0J5IzfPFDVcRX6mxdvCwGU8Tk/rLWNF7oFanV50CMDlJ3CgXuwA630pFsAMlZQOePyhYYlclUePPrALRO7pj5ztp7cERQ9kHgLMmeh/KWdIVZtD1wr1gilwAZUwjHnDmR+QniBd0D5D8X2jYqn0WG/9+t4CS2ZoGIXf0ZeBOUzAF2KlyNQ30Pg+1psofhSyBRj6S2f1o1L8rYTTIR/bK3rRZ/Sb83Ti6QsuDUihrsOIrwj60rAhi4IdgOab635QCo2i8XmJFbB8y/30008vL74KH4VlnE4w8yWcXMmRxQC0zTbbrNl5553LzK3fsDRIGwIixoGx8PFioKFnvpYRyCeqWEjyNlLEevUDV1+fKw1wTmFMpm9aQbiGu/emM0vihyClEzYKq2I41EDraD4+ayZDIIYLdQP+Yj7IXVvJwITJt2h9IJhsyX9YigEALpMyz4mV7T7gMiTGJQnQ1UvuM1FfcEEwR9oZaJhoPYiC/V6MNQ9IFg/ZPgDCyrh2hIFeIBAaUzwIaaxh0JN6dbOWXnStX9AcpJyFmAao8okCvpb1Ru2n7ABjmHbREeLIn3rqqcW3ou8YBvL2dcPoUb14GKTuvuBScQqur1kUfldAwi9SqbPhEkE+woh0NUMBY0nQ558G+XaBn801kdB4AOaD4G0xH9rOcb+g/cEsowGfq/cxTB/xzRgd3frIiI/6OVuJB2CGg8yzGJ7JE6smX+6nq2Agn6s2vxlvFWjtw3cifHHOkAhcGDvppJPKbyiH8VQOXHUYgNWAS7qpzsDlF9J838pvARI4ylA9VZ7FEMaCGBbN0LkiS5cuLZ9/0rZYnWHaGZ/L7w0BFoPAQjIGrKbfcaJz1jO6By4EXHQ4HfUFFzCkUIUoWKGUysnzG9bGasxAuHA/2mTmyPlENQB6AVYS9PmnwerVax2+r6UuDevXe/oUPe+jI3suhPayZKHZyDJ5c1YGK+WDeWRsGOTfqrf+rBP9qrs2BrWhSHn1uS+4kphJBhKVOjTWEOiXRk877bTyFB3iWRQ9SljApYwALMMjxvoxl7qVZ3aYuuu1H2GLmciL7MjKtc7E76Vk8q6VPRs5kJ/fy/TdNaOOe/U4zPojX8DCg/odwhM3Xb0DaYZpBq7aDLvGgI/ie6B5wZU+gYZbdPMg2wfYDJ0BVpiQD0VoCZ/qrFcxz3y8mvBjsbYGWh2/WK4juwxF2kUWkeGw7SRHP7xKjmQd/bn3g6eGTQCO7muj0M+t6evQa4TCWal6GNJocdD76Ec/uvherJbhSrjx2w9YCqtnhfLlGMRypVGceKRc5Tn4eWnsYj1rM/nrSA4AYDkCOvHDkMmXLy/qwHQMbGS5/fbbF7cHuOhYGCOj3rhG/ertCy6FUbAKgKuepqoYeZBtfKZwMzhnaa1LEQafqe5pKwKujPOpywQiQFf3YgVV2sUdYE1MqhzaHmtCtsOSyZe66MywmImZT38qnx4RgHPqpUW1PkvAFP/6gkuh9WGcBzRhQOfNH469WYXHBIbHfJXOOpQZHoYJSANC+dJf7qc7qwcZCmo+WMj6frFekx35pn3p7GRCBv2IlQOI888/v5wzyxZ29NFHN19rf7qZJVKPB9bK9P17n9UEZvWpG3H2w0c6/Uz19wXXTJnF2WdldgHpQIUBQxe/C7r94pUftAxj8miwT2JnHUxYR+ORAMujkxtZnOnB0Kdz+y1wlhBZpKY/BuBZz3rWSJgZGly40LM0Ytdddy3IBirIhnxMH3vssQVo7pnaWDBpOhqvBHRk/hQCLGTk8fPLloxYLDphDKzSW+bZeOONSzogHIaGBpeVcmbVAqo1L0ccP4AznFkHe81rXlP45JQCozOT29F4JUDGwOS5ZPwlC9F8LUOfYU68OP7sTjvtVBji1w2rn1VaKzPUnmEOJz8MoFgsPphHBkivASQ9AKN+SDy/WCGMz5bxvGTo/o1cAkADKDq6Mz359RC/hmEbDeMgjrP+tre9rRgHQ6XwYdfRhjYdGECGOABae+21m9122634YZj2wqwGAuHrX//6YrGkB6xrwoNnbZ1rogfExz3iiCPKO4lGFK+PIaPOlu0vigAdYDEQwwJLuUODSyF6BEsEYJYqdtlll/IzI8wtwJkpGvftFbInyw9yo87nKmIY6z+6QTq5H3M/7rjjyo4SgOIDc2lsqdlrr70m+chEi5UbhkYCLoyzRIZFTqPp7L777lsAZ1bIv4pjyfeyKY3VGmQqPUzjurz/fTMewDjtBx544ORyBCNgdwUA+eW2TTbZpIiLXow+0ekwMhwaXIBj8Q1lFugag4ceemjZMWGbDPBpJAAecMABzdlnny1Zua/XXrJQZyhl8TqaWQLkRO5A4doBSJGp3IZF6432w8WfAiwjiB8b3WGHHUo4a2b2iFi0Yf3hocGFiRBmYkqBy28G+mk3IBGu0RoHQMcff3yZCms4a0coiDUzxALhYn9uGLkNc9a5DW98qADKJCoyNWK8+MUvLrNFyw3WJOmADwxIFkzzTmivmxKdzJa/ocFVV4xpoAgBmOFRYzUGWMS7tjHtqKOOmnTqE65BrByhdNRfAvGPpCRfcqstjlV4i6V2Pei0Weuiq7333rsYAHnjtriWbhS06kEHHXTwMAVhhNMOFM6ceA3WQFYpb434AW5pOPZMuF7jSyry6Dn53cS6LBautzcNw+tizMtqsVgZQXRcwBHmofRrX/vaMmzSh/AAx2LpS1/60iKSuB+RtY5ODyjncrOC/4YGl8oNeQGX+l0DmN4g3tsj1k08fY9VYs6Rlw00nEPJwsmnsYQmbUczS4CsAYsOdFpDnzD76fbff/9yTdYmVnxej+a22WabZr/99isdPbtNsopPZ4wCoEk/5+DS/FgwzDgAA4OA4946ivWTZcuWlUYRgDwaYPbovMEGGxSAySte3o5mlgA/lrzImGUiN89yTaZYLyNErBmH3hOUww8/vGybjm4ya3evPGBVDpANQ0NbLkxomHMsleuQOA9JjfUbbrhhGfs1IA2xRcczLT4BYXgEkSGyNvcprztfVQIsfcBAXiwWYPFpbeT0qphRgMw32mij5rDDDisv1bJmOn8NINavN+yqta3Y3dAOfcZwvhbG3BuzHRoQk2yay7fiYDLRzLMlDM+8PI1nnvU4wmHeUUz1ijXpmpW6nlF7IXmfffYpK+6+7eDBNKIXndsSkJ869qE+gKMj4ET0hKRFXBOjyTA0tOWqkc9i5SForsOsszAm2LZoIPz4xz/erNN+19xwGfLSq9f1bREBxljBOJnuCUTDUyYrWMdLI976T+pP+fPtjFeHNuTAY0YDcUYE7Yls3Wsv2cvj3syb844ACYDkJaslS5aU5QiPd4RlLYt8MhFQdq3LuCalwFn+Gxpc/erVGEQ4rtM4DbE24wk9C0ZIzDJrZYj0YFW8J/fxKVIXQTgIRzkELX/Kl45yciTffDzjGe+htMEZsSDar53k45xDvBm1dSyPdXRYYJKHRTNBsoBtycFCqTL5X+rT6cbd8VYauAAgvY/SWSwf1LAcASSEIo1ZJSEZJm2TJhyA09sIV7oIRY8FMhQFKTtUXydsvp3Dd84AUPPtWhuFAxUCKO7EBe2uX9+NsNKeDQLkxbGXxtAXYMnHJzNyxFqRX12XNKOk/2lilKVWZQEUIiDgAZDcewThzRMg42fFUhGKdBx9foJPM+lxQAVc/DflZnZE6IREAepYSARUDrznqPmv2wNAAAEgvhnhJQqbAQDJJMhOBtf82XXXXbfsgLBDGLFmKB2T/KKLEjGGf0Pv5+rHEyuUnkJQwAEIGhlh8rlsBeHQizMM6GVx6Fkz70Ba7c+7kGY/iQ8PlKQO54AtcfP1jF9yQJFHL6+WEACKL0VWr3zlK5szzjijWDJyzDNBsjU73Hrrrcsa16abblqK0jHJSlqgIlvDpk5JVuOilQoujdAYvQ8RJouVpQdW6oQTTiiNZ8UIg/XSC4GUUPgOHFd59WTpCCkKIrz0dmEZSkqF8/AfeeAz/IfFtIEl4jsh28XtICUTnYc74QA89w7rWIccckiZDAGazwCYCRoNEKBJB6g1sEvkiP+NHVy9QpuKf+tglicQAR5zzDHFjGfFnkAIh9l3zRLyNexByn2GSGUA2EKxXlPJh9Jz6Bx8z1NOOaVMfviiAZtrPpYOCmAeQu++++6lw+mMRoCayAWlw+msKatON6rrlQYuJhvxvdI49+lVAYR4HzZ51ateVZ6NeV3NsCAdU86aETygeTAOiEw+IcmLYuopLtclYh7+mw5crLt2skLeXvfaFyukY9lBql0mRYDFCu25557l4y+aKB/rxHVw1jGVx8dyLV4ceY5TPmMHVz99Musx2YYAPRBZp7FM4fkYQOmJACgthRCUcMDih2277bZlf778ykSsGaEGdIQK5AQqjuBrEl4Lu1Z8HS5P4pSn/NSR8tTrUI+86kbqlDflSePekU5n9sdSmcjYIgMEynHNv9KRhOlwXgPbbrvtyuKo8lkzMiIblHrKzUr+N+fg0vgoWW9iyiMYAONfcGB9CJa1Ah6KAi6Clta1YZUjC2RWoTOJAMgorZategFW3eLDQ9KoQ946PIoKsKRN2UASUKs7bZCnTp/ye8/qO+ussxrfJD3nnHNK58qw5ayMJe1iqPK8C8qi63i+keYJR0g54dkMsXfSk3Qr4zyvwEWZlBRgRACsA1/Mu3Z8LAolZAuswKdXsxyUqwx7wk3TffkwuwEMH/IQtvS9JF8UE8D0ppnqHr+9Vks6gAdeZbIkSSNcHnXoSJzu8847r3xzTAfSHulZJmlMWIThL1Zpjz32KO+Iakd8ptQnb8Cls2YkmIr3cYfNObgAh5AjEA0W5j6933BgRmnhkBXjgwAKwHBovXhrSLXQyrpRBKtGGT4D5LOLgJZebDih7JTfK2SAAADl4I3VyCGtunNIG5IGIJxrkjavaiUOkJa1O0R8dA0IWBlxwIA399onn3VAxFqZxHh8hj/tST55AmB8k597dc8VzTm4KEevo+hYDMJhhaIIAs8QRmB8sSOPPLKAzXYd+fRiQJDOoQfr9YBESfL5Mh+QbbXVVsWi1YCOEtQpPHUrqzdMWnw7SzsVoMRrBxDID+wslI7hrMPIK40jZUkrr8O1R2PO3mbfcccdC06UqT06lHaKrymyMwKkXXX8yrqec3ARDGFRImEEYLUACF+4M2vEOrFuJ598cpmmc4Dzqw82yylHPCFTUqwBoDn0+CWt/8Lq8VkMneuvv36z3nrr1dWWdTV5a7AlQQDWyy8rZIXcjC6AsjvBs1LrcspjYZUZ4AYA5IBvwAEqw76Nfd5D0FHk177k1ZlCkVHuc07ZuV+Z53kBLg0mWEfvEJnhpBaKHms4sGuC1TPE+E6qRyFWsCmN0jPsKZPCKADg1CNeWKwb0Amzv8yODI+mOMqATJninFGskrMdHBxufhH/CQDc40G8s3x4CR941gZ16gji8CItMvxZLDak86mUk7rjR+mUZBCrDpSAVC/JyNdr1UoFK+nfnIOLoNMDKb++JzBmH4ljCQgLABArZo0HESQLwYfJuhAQUYY8wJHDvYMyKJlio2BlhY+AkGKliYLVJc6ZFXSWxzmWInVIF1BJExAAlaE87WKRfETPs0C/rYSUBXDSIteRVeqp5SWNcHWjALHczMG/OQfXsG2mHESoAECwAHPuueeWw3oRITtQfDPpKYayA57aOkkrTfwXaaI04QGS8pC4hOPJIY3wWCdhynMWpuP4pqxngL6vYZEUiUf4UeZCpQUPLgqkhKnILJJ/w9/xi2rWkewVM9QClbxmoRRIob2HcEsCM1ENPuCOdQRGhzJYHOnEeaqw+eabl2eAJhiGMQBNG8IX8CX/TPXP57hFBS6KNAzF1yL4WA8WhJL5ZIZPO159bQ/opHHILx1FZxhk3cQBnjOidGmifOHqlSZliFOWiYIXT1gmFoqTHpJHPaHkda9eR9yCpFlI5wUPrtonIfgMO5RLcYBG+RSFYl0oEhgomCXzNMCyhlmn66ydWVuTJuBRRsCnLBMCfhc/kFUy47SLw0xUHMtUgxAvtX9nmQQJ45yH8Mdhl3+h0oIHF6UjimYpegmAkDhKRhQMhIBp2ItVolBpKDppk34qcNVppFOOI9avN16alJO64qCLw5fwDJHC3C9UWvDgokgK46tQFGDMlqYCorIomOIdCGgcvWCWrjcMiPFUA6YUMsU/+aVH/DBldeCaQlArK4gCYoliveInAZtr4b3WKNaqV+m94Oi9720XUPdSDYjMJllJvIjLsOpsWOS8Swew4vGWMvC+UGnBWy5KYLnq4SWKCfAop9fyRGnCpxq+olBlx0oFaMp3yJtykr4+S5M8dfhU1zXPdXzC67CFcr3gwbVQBH1N5POqu+WuiRLo2jw2CXTgGptou4I7cHUYGJsEOnCNTbRdwR24OgyMTQIduMYm2q7gDlwdBsYmgQ5cYxNtV3AHrg4DY5NAB66xibYr+P8Bw8bYowWLvewAAAAASUVORK5CYII=" + } } ], "linkId": "PR-name-text", @@ -247,6 +255,14 @@ "expression": "ContactPoint", "name": "contactPoint" } + }, + { + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { + "id": "okImage", + "contentType": "image/png", + "data": "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" + } } ], "item": [ From badcbe879c6df2bed2060f6964150c3f1a5773d6 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Mon, 4 Oct 2021 18:25:39 +0300 Subject: [PATCH 02/75] Add itemImage extension support on display questions --- .../MoreQuestionnaireItemComponents.kt | 6 ++-- ...estionnaireItemDisplayViewHolderFactory.kt | 16 +++++++++ .../questionnaire_item_display_view.xml | 34 ++++++++++++++----- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index ea77ed2529..320bab7e66 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -18,13 +18,14 @@ package com.google.android.fhir.datacapture import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.util.Base64 +import java.util.Locale import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType -import java.util.Locale internal enum class ItemControlTypes( val extensionCode: String, @@ -190,7 +191,8 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { if (url.contains("/Binary/")) { return DataCaptureConfig.attachmentResolver?.run { resolveBinaryResource(url)?.run { - BitmapFactory.decodeByteArray(this.data, 0, this.data.size) + val byteArray = Base64.decode(this.dataElement.valueAsString, Base64.DEFAULT) + BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) } } } else if (url.startsWith("https") || url.startsWith("http")) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 348a26a400..22aa41cf0d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -17,11 +17,17 @@ package com.google.android.fhir.datacapture.views import android.view.View +import android.widget.ImageView import android.widget.TextView import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.fetchBitmap +import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.localizedPrefix import com.google.android.fhir.datacapture.localizedText import com.google.android.fhir.datacapture.validation.ValidationResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch internal object QuestionnaireItemDisplayViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_display_view) { @@ -29,11 +35,13 @@ internal object QuestionnaireItemDisplayViewHolderFactory : object : QuestionnaireItemViewHolderDelegate { private lateinit var prefixTextView: TextView private lateinit var textView: TextView + private lateinit var itemImageView: ImageView override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem override fun init(itemView: View) { prefixTextView = itemView.findViewById(R.id.prefix) textView = itemView.findViewById(R.id.text_view) + itemImageView = itemView.findViewById(R.id.itemImage) } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { @@ -50,6 +58,14 @@ internal object QuestionnaireItemDisplayViewHolderFactory : } else { View.VISIBLE } + + questionnaireItemViewItem.questionnaireItem.itemImage?.let { + GlobalScope.launch { + it.fetchBitmap()?.run { + GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + } + } + } } override fun displayValidationResult(validationResult: ValidationResult) { diff --git a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml index a0d4209c8d..9daf825d28 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml @@ -20,20 +20,38 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginVertical="@dimen/item_margin_vertical" - android:orientation="horizontal" + android:orientation="vertical" > - + android:orientation="horizontal" + > + + + + - + + + From a2575d52a47badfba8bc8e7d2c425dac99878b04 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Mon, 4 Oct 2021 18:38:14 +0300 Subject: [PATCH 03/75] Add sample implementation of itemImage extension --- reference/src/main/assets/binary.json | 6 ++ .../new-patient-registration-paginated.json | 23 ++++++++ .../main/assets/screener-questionnaire.json | 42 ++++++++------ .../android/fhir/reference/FhirApplication.kt | 3 + .../fhir/reference/PatientListFragment.kt | 10 ++++ .../reference/ReferenceAttachmentResolver.kt | 44 +++++++++++++++ .../fhir/reference/api/NormalService.kt | 55 +++++++++++++++++++ 7 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 reference/src/main/assets/binary.json create mode 100644 reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt create mode 100644 reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt diff --git a/reference/src/main/assets/binary.json b/reference/src/main/assets/binary.json new file mode 100644 index 0000000000..db7d9b5399 --- /dev/null +++ b/reference/src/main/assets/binary.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Binary", + "id": "syringe-image", + "contentType": "image/png", + "data": "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" +} \ No newline at end of file diff --git a/reference/src/main/assets/new-patient-registration-paginated.json b/reference/src/main/assets/new-patient-registration-paginated.json index 6ae9d9ba60..1073fe4067 100644 --- a/reference/src/main/assets/new-patient-registration-paginated.json +++ b/reference/src/main/assets/new-patient-registration-paginated.json @@ -33,6 +33,14 @@ { "url": "content", "valueString": "Maelezo ya mteja" + }, + { + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { + "id": "syringe-image", + "contentType": "image/png", + "url": "https://mpng.subpng.com/20180124/gpw/kisspng-syringe-injection-cartoon-blue-syringe-5a68881609c163.27719744151680002204.jpg" + } } ], "url": "http://hl7.org/fhir/StructureDefinition/translation" @@ -52,6 +60,14 @@ "expression": "HumanName", "name": "humanName" } + }, + { + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { + "id": "syringe-image-2", + "contentType": "image/png", + "url": "https://fhir.labs.smartregister.org/fhir/Binary/syringe-image" + } } ], "item": [ @@ -300,6 +316,13 @@ "expression": "Patient.telecom.value", "name": "patientTelecom" } + }, + { + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { + "id": "okImage", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Combotrans.svg/1200px-Combotrans.svg.png" + } } ], "linkId": "PR-telecom-value", diff --git a/reference/src/main/assets/screener-questionnaire.json b/reference/src/main/assets/screener-questionnaire.json index 6c263e6b19..3e07caa37c 100644 --- a/reference/src/main/assets/screener-questionnaire.json +++ b/reference/src/main/assets/screener-questionnaire.json @@ -526,35 +526,35 @@ "valueCoding": { "code": "35489007", "display": "Depression", - "system" : "http://snomed.info/sct" + "system": "http://snomed.info/sct" } }, { "valueCoding": { "code": "161445009", "display": "Diabetes", - "system" : "http://snomed.info/sct" + "system": "http://snomed.info/sct" } }, { "valueCoding": { "code": "161501007", "display": "Hypertension", - "system" : "http://snomed.info/sct" + "system": "http://snomed.info/sct" } }, { "valueCoding": { "code": "56265001", "display": "Heart disease", - "system" : "http://snomed.info/sct" + "system": "http://snomed.info/sct" } }, { "valueCoding": { "code": "161450003", "display": "High blood lipids", - "system" : "http://snomed.info/sct" + "system": "http://snomed.info/sct" } } ] @@ -922,21 +922,21 @@ "valueCoding": { "code": "NV", "display": "Not vaccinated", - "system" : "custom" + "system": "custom" } }, { "valueCoding": { "code": "PV", "display": "Partially vaccinated", - "system" : "custom" + "system": "custom" } }, { "valueCoding": { "code": "FV", "display": "Fully vaccinated", - "system" : "custom" + "system": "custom" } } ] @@ -950,7 +950,7 @@ "question": "4.1.0", "operator": "=", "answerCoding": { - "system" : "custom", + "system": "custom", "code": "PV" } } @@ -960,21 +960,21 @@ "valueCoding": { "code": "AZ", "display": "AstraZeneca", - "system" : "custom" + "system": "custom" } }, { "valueCoding": { "code": "Pfizer", "display": "Pfizer BioNTech", - "system" : "custom" + "system": "custom" } }, { "valueCoding": { "code": "Moderna", "display": "Moderna", - "system" : "custom" + "system": "custom" } } ] @@ -988,7 +988,7 @@ "question": "4.1.0", "operator": "=", "answerCoding": { - "system" : "custom", + "system": "custom", "code": "PV" } } @@ -1003,7 +1003,7 @@ "question": "4.1.0", "operator": "=", "answerCoding": { - "system" : "custom", + "system": "custom", "code": "FV" } } @@ -1038,7 +1038,7 @@ "question": "4.1.0", "operator": "=", "answerCoding": { - "system" : "custom", + "system": "custom", "code": "FV" } } @@ -1078,7 +1078,17 @@ { "text": "Add instructions for capturing temperature", "type": "display", - "linkId": "5.1.0" + "linkId": "5.1.0", + "extension": [ + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment": { + "id": "okImage", + "contentType": "image/png", + "data": "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" + } + } + ] }, { "linkId": "5.2.0", diff --git a/reference/src/main/java/com/google/android/fhir/reference/FhirApplication.kt b/reference/src/main/java/com/google/android/fhir/reference/FhirApplication.kt index ab15b75c1c..f11ac0f821 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/FhirApplication.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/FhirApplication.kt @@ -20,6 +20,7 @@ import android.app.Application import android.content.Context import com.google.android.fhir.FhirEngine import com.google.android.fhir.FhirEngineProvider +import com.google.android.fhir.datacapture.DataCaptureConfig import com.google.android.fhir.reference.data.FhirPeriodicSyncWorker import com.google.android.fhir.sync.Sync @@ -30,6 +31,8 @@ class FhirApplication : Application() { override fun onCreate() { super.onCreate() Sync.oneTimeSync(this) + + DataCaptureConfig.attachmentResolver = ReferenceAttachmentResolver(this) } private fun constructFhirEngine(): FhirEngine { diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt index 531e072ce0..c5184b98f3 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt @@ -32,9 +32,12 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView +import ca.uhn.fhir.context.FhirContext import com.google.android.fhir.FhirEngine import com.google.android.fhir.reference.PatientListViewModel.PatientListViewModelFactory import com.google.android.fhir.reference.databinding.FragmentPatientListBinding +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Binary class PatientListFragment : Fragment() { private lateinit var fhirEngine: FhirEngine @@ -128,6 +131,13 @@ class PatientListFragment : Fragment() { } setHasOptionsMenu(true) (activity as MainActivity).setDrawerEnabled(true) + + val binaryJson = + requireActivity().assets.open("binary.json").bufferedReader().use { it.readText() } + + val binaryResource = + FhirContext.forR4().newJsonParser().parseResource(Binary::class.java, binaryJson) + runBlocking { fhirEngine.update(binaryResource) } } override fun onDestroyView() { diff --git a/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt new file mode 100644 index 0000000000..0a6204dea5 --- /dev/null +++ b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.reference + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.google.android.fhir.datacapture.AttachmentResolver +import com.google.android.fhir.reference.api.NormalService +import org.hl7.fhir.r4.model.Binary + +/** Created by Ephraim Kigamba - nek.eam@gmail.com on 04-10-2021. */ +class ReferenceAttachmentResolver(val context: Context) : AttachmentResolver { + + override suspend fun resolveBinaryResource(uri: String): Binary? { + return uri.substringAfter("Binary/").substringBefore("/").run { + FhirApplication.fhirEngine(context).load(Binary::class.java, this) + } + } + + override suspend fun resolveImageUrl(uri: String): Bitmap? { + return NormalService.create().fetchImage(uri).execute().run { + if (this.body() != null) { + BitmapFactory.decodeStream(this.body()?.byteStream()) + } else { + null + } + } + } +} diff --git a/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt b/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt new file mode 100644 index 0000000000..6fb4437ba0 --- /dev/null +++ b/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.reference.api + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import retrofit2.http.Url + +/** Created by Ephraim Kigamba - nek.eam@gmail.com on 04-10-2021. */ + +/** hapi.fhir.org API communication via Retrofit */ +interface NormalService { + + @GET fun fetchImage(@Url url: String): Call + + companion object { + const val BASE_URL = "https://hapi.fhir.org/baseR4/" + + fun create(): NormalService { + val gson: Gson = GsonBuilder().setLenient().create() + + val logger = HttpLoggingInterceptor() + logger.level = HttpLoggingInterceptor.Level.BODY + + val client = OkHttpClient.Builder().addInterceptor(logger).build() + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build() + .create(NormalService::class.java) + } + } +} From 81a294126bd457695579ddeb3d022200d1f18eb0 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Tue, 5 Oct 2021 15:58:44 +0300 Subject: [PATCH 04/75] Add tests for Attachment#fetchBitmap extension --- buildSrc/src/main/kotlin/Dependencies.kt | 2 + datacapture/build.gradle.kts | 1 + datacapture/jacoco.exec | Bin 0 -> 4094 bytes .../MoreQuestionnaireItemComponents.kt | 9 +- .../MoreQuestionnaireItemComponentsTest.kt | 100 +++++++++++++++++- 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 datacapture/jacoco.exec diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 4ef46499ea..01a6f542e8 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -108,6 +108,7 @@ object Dependencies { const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}" const val truth = "com.google.truth:truth:${Versions.truth}" const val flexBox = "com.google.android.flexbox:flexbox:${Versions.flexBox}" + const val timber = "com.jakewharton.timber:timber:${Versions.timber}" object Versions { object Androidx { @@ -157,5 +158,6 @@ object Dependencies { const val junit = "4.13" const val mockitoKotlin = "3.2.0" const val robolectric = "4.5.1" + const val timber = "5.0.1" } } diff --git a/datacapture/build.gradle.kts b/datacapture/build.gradle.kts index 232f28549e..f35286205b 100644 --- a/datacapture/build.gradle.kts +++ b/datacapture/build.gradle.kts @@ -96,6 +96,7 @@ dependencies { implementation(Dependencies.Lifecycle.viewModelKtx) implementation(Dependencies.material) implementation(Dependencies.flexBox) + implementation(Dependencies.timber) testImplementation(Dependencies.AndroidxTest.core) testImplementation(Dependencies.junit) diff --git a/datacapture/jacoco.exec b/datacapture/jacoco.exec new file mode 100644 index 0000000000000000000000000000000000000000..4998804535faed124cf2ce9b414825876b55af4d GIT binary patch literal 4094 zcmZQPa6o`vfI%)hGd(dkDN#2hBQq~MATdQZ#MHt-H_6l>H8IH~iGhK!CLq_e0z$j| z5}a)Pj%T({PX&W>a(=FUdVYR-PO5%lUP@7ZW{Q4VMrM(IN@7W3a$-SAX;G?vU}Hjx&Jfd` z^YhX&)7cCKYZ+h8U@_k`iNTi`69~9T#W_E>ATbZ2lkGXxW3vTuG- zDu(GGw*Pq0;V#!QpNl&jQ`7j{{MfE zm*L0H{Q`FXnHd=B=P>|*;KXg~sy++N5@0YP#v)Km0YkT>C_e`jO~u@=*%|RB7c;}e zlvESbWN0RO%u%_$)($16FAv)8-E@K MO_@w!%Z`Bo0O+{KZ2$lO literal 0 HcmV?d00001 diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 320bab7e66..d36438b99e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -26,6 +26,7 @@ import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType +import timber.log.Timber internal enum class ItemControlTypes( val extensionCode: String, @@ -185,8 +186,14 @@ val Attachment.isImage: Boolean get() = this.hasContentType() && contentType.startsWith("image") suspend fun Attachment.fetchBitmap(): Bitmap? { + // Attachment's with data inline need the contentType property + // Conversion to Bitmap should only be made if the contentType is image if (data != null) { - return BitmapFactory.decodeByteArray(data, 0, data.size) + if (isImage) { + return BitmapFactory.decodeByteArray(data, 0, data.size) + } + Timber.e(Throwable("Attachment is not of contentType image/**")) + return null } else if (url != null) { if (url.contains("/Binary/")) { return DataCaptureConfig.attachmentResolver?.run { diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 52c45ce9e7..3a724da9b0 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -16,9 +16,14 @@ package com.google.android.fhir.datacapture +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.os.Build +import android.util.Base64 import com.google.common.truth.Truth.assertThat -import java.util.Locale +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Binary import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -29,8 +34,13 @@ import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.utils.ToolingExtensions import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import java.nio.charset.Charset +import java.util.Locale @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) @@ -364,4 +374,92 @@ class MoreQuestionnaireItemComponentsTest { ) .isEqualTo(true) } + + @Test + fun `fetchBitmap() should return null when Attachment has data and incorrect contentType`() { + val attachment = + Attachment().apply { + data = "some-byte".toByteArray(Charset.forName("UTF-8")) + contentType = "document/pdf" + } + val bitmap: Bitmap? + runBlocking { bitmap = attachment.fetchBitmap() } + assertThat(bitmap).isNull() + } + + @Test + fun `fetchBitmap() should return Bitmap when Attachment has data and correct contentType`() { + val attachment = + Attachment().apply { + data = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7".toByteArray(Charset.forName("UTF-8")) + contentType = "image/png" + } + val bitmap: Bitmap? + runBlocking { bitmap = attachment.fetchBitmap() } + assertThat(bitmap).isNotNull() + } + + @Test + fun `isImage() should true when Attachment contentType is image`() { + val attachment = Attachment().apply { contentType = "image/png" } + assertThat(attachment.isImage).isTrue() + } + + @Test + fun `isImage() should false when Attachment contentType is not image`() { + val attachment = Attachment().apply { contentType = "document/pdf" } + assertThat(attachment.isImage).isFalse() + } + + @Test + fun `fetchBitmap() should return Bitmap and call AttachmentResolver#resolveBinaryResource`() { + val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } + val binary = + Binary().apply { + contentType = "image/png" + data = + "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7".toByteArray( + Charset.forName("UTF-8") + ) + } + val bitmap: Bitmap? + val attachmentResolver: AttachmentResolver = mock() + runBlocking { + Mockito.`when`(attachmentResolver.resolveBinaryResource("https://hapi.fhir.org/Binary/f006")) + .doReturn(binary) + } + DataCaptureConfig.attachmentResolver = attachmentResolver + + runBlocking { bitmap = attachment.fetchBitmap() } + + assertThat(bitmap).isNotNull() + runBlocking { + Mockito.verify(attachmentResolver).resolveBinaryResource("https://hapi.fhir.org/Binary/f006") + } + } + + @Test + fun `fetchBitmap() should return Bitmap and call AttachmentResolver#resolveImageUrl`() { + val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } + val byteArray = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + val attachmentResolver: AttachmentResolver = mock() + runBlocking { + Mockito.`when`( + attachmentResolver.resolveImageUrl("https://some-image-server.com/images/f0006.png") + ) + .doReturn(bitmap) + } + DataCaptureConfig.attachmentResolver = attachmentResolver + + val resolvedBitmap: Bitmap? + runBlocking { resolvedBitmap = attachment.fetchBitmap() } + + assertThat(resolvedBitmap).isEqualTo(bitmap) + runBlocking { + Mockito.verify(attachmentResolver) + .resolveImageUrl("https://some-image-server.com/images/f0006.png") + } + } } From 585914b4f902f5fc70c2fa759e8c0815efd957a5 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Tue, 5 Oct 2021 17:37:00 +0300 Subject: [PATCH 05/75] Fix itemImage not displayed centered on group question --- ...leLineViewHolderFactoryInstrumentedTest.kt | 35 ++++++++++++++++++ .../questionnaire_item_group_header_view.xml | 36 +++++++++++-------- .../MoreQuestionnaireItemComponentsTest.kt | 9 +++-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index dd86ce2807..68707460f4 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -16,6 +16,7 @@ package com.google.android.fhir.datacapture.views +import android.util.Base64 import android.widget.FrameLayout import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper @@ -26,6 +27,8 @@ import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.material.textfield.TextInputEditText import com.google.common.truth.Truth.assertThat +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType @@ -167,4 +170,36 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { assertThat(questionnaireItemViewItem.questionnaireResponseItem.answer.size).isEqualTo(0) } + + @Test + @UiThreadTest + fun shouldShowImageWhenItemImageExtensionAvailable() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + prefix = "" + extension = + listOf( + Extension().apply { + url = "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage" + setValue( + Attachment().apply { + id = "ok-image" + contentType = "image/png" + data = + Base64.decode( + "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + Base64.DEFAULT + ) + } + ) + } + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).isVisible).isTrue() + } } diff --git a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml index cdd5507223..6425606470 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml @@ -20,31 +20,39 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginVertical="@dimen/item_margin_vertical" - android:orientation="horizontal" + android:orientation="vertical" > - + android:orientation="horizontal" + > - + + + + + diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 3a724da9b0..36ec4b2110 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -21,6 +21,8 @@ import android.graphics.BitmapFactory import android.os.Build import android.util.Base64 import com.google.common.truth.Truth.assertThat +import java.nio.charset.Charset +import java.util.Locale import kotlinx.coroutines.runBlocking import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.Binary @@ -39,8 +41,6 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import java.nio.charset.Charset -import java.util.Locale @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) @@ -391,7 +391,10 @@ class MoreQuestionnaireItemComponentsTest { fun `fetchBitmap() should return Bitmap when Attachment has data and correct contentType`() { val attachment = Attachment().apply { - data = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7".toByteArray(Charset.forName("UTF-8")) + data = + "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7".toByteArray( + Charset.forName("UTF-8") + ) contentType = "image/png" } val bitmap: Bitmap? From 4500c054aea973470a64dfbe3d64f04f42b234e2 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Thu, 7 Oct 2021 18:36:50 +0300 Subject: [PATCH 06/75] Fix repeating images of itemImage and cleanup code - Add extension properties and method documentation in MoreQuestionnaireItemComponents.kt - Log errors trying to fetch Bitmap from Attachment and Binary Resource - Remove test images in reference app - Add thermometer instruction image in reference-app screener questionnaire - Sync thermometer Binary resource --- datacapture/jacoco.exec | Bin 4094 -> 0 bytes .../MoreQuestionnaireItemComponents.kt | 40 +++++++++---- ...estionnaireItemDisplayViewHolderFactory.kt | 7 ++- ...stionnaireItemEditTextViewHolderFactory.kt | 9 ++- ...QuestionnaireItemGroupViewHolderFactory.kt | 7 ++- .../questionnaire_item_display_view.xml | 1 + .../questionnaire_item_edit_text_view.xml | 1 + .../questionnaire_item_group_header_view.xml | 1 + reference/src/main/assets/binary.json | 6 -- .../new-patient-registration-paginated.json | 39 ------------- .../main/assets/screener-questionnaire.json | 7 +-- .../fhir/reference/PatientListFragment.kt | 10 ---- .../reference/ReferenceAttachmentResolver.kt | 18 +++--- .../fhir/reference/api/HapiFhirService.kt | 4 ++ .../fhir/reference/api/NormalService.kt | 55 ------------------ .../reference/data/FhirPeriodicSyncWorker.kt | 6 +- 16 files changed, 76 insertions(+), 135 deletions(-) delete mode 100644 datacapture/jacoco.exec delete mode 100644 reference/src/main/assets/binary.json delete mode 100644 reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt diff --git a/datacapture/jacoco.exec b/datacapture/jacoco.exec deleted file mode 100644 index 4998804535faed124cf2ce9b414825876b55af4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4094 zcmZQPa6o`vfI%)hGd(dkDN#2hBQq~MATdQZ#MHt-H_6l>H8IH~iGhK!CLq_e0z$j| z5}a)Pj%T({PX&W>a(=FUdVYR-PO5%lUP@7ZW{Q4VMrM(IN@7W3a$-SAX;G?vU}Hjx&Jfd` z^YhX&)7cCKYZ+h8U@_k`iNTi`69~9T#W_E>ATbZ2lkGXxW3vTuG- zDu(GGw*Pq0;V#!QpNl&jQ`7j{{MfE zm*L0H{Q`FXnHd=B=P>|*;KXg~sy++N5@0YP#v)Km0YkT>C_e`jO~u@=*%|RB7c;}e zlvESbWN0RO%u%_$)($16FAv)8-E@K MO_@w!%Z`Bo0O+{KZ2$lO diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index d36438b99e..a365927786 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -21,6 +21,7 @@ import android.graphics.BitmapFactory import android.util.Base64 import java.util.Locale import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Binary import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Questionnaire @@ -176,15 +177,37 @@ fun QuestionnaireResponse.QuestionnaireResponseItemComponent.addNestedItemsToAns private inline fun Questionnaire.QuestionnaireItemComponent.getNestedQuestionnaireResponseItems() = item.map { it.createQuestionnaireResponseItem() } +/** The Attachment defined in the [EXTENSION_ITEM_IMAGE] extension where applicable */ internal val Questionnaire.QuestionnaireItemComponent.itemImage: Attachment? get() { val extension = this.extension.singleOrNull { it.url == EXTENSION_ITEM_IMAGE } return if (extension != null) extension.value as Attachment else null } +/** Whether the Attachment has a [Attachment.contentType] for an image */ val Attachment.isImage: Boolean get() = this.hasContentType() && contentType.startsWith("image") +/** Whether the Binary has a [Binary.contentType] for an image */ +private fun Binary.isImage(): Boolean = this.hasContentType() && contentType.startsWith("image") + +/** Decodes the Bitmap from the Base64 encoded string in [Bitmap.data] */ +private fun Binary.getBitmap(): Bitmap? { + return if (isImage()) { + Base64.decode(this.dataElement.valueAsString, Base64.DEFAULT).let { byteArray -> + BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + } + } else { + Timber.e(Throwable("Binary does not have a contentType image")) + null + } +} + +/** + * Returns the Bitmap defined in the attachment as inline Base64 encoded image, Binary resource + * defined in the url or externally hosted image. Inline Base64 encoded image requires to have + * contentType starting with image + */ suspend fun Attachment.fetchBitmap(): Bitmap? { // Attachment's with data inline need the contentType property // Conversion to Bitmap should only be made if the contentType is image @@ -194,18 +217,15 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { } Timber.e(Throwable("Attachment is not of contentType image/**")) return null - } else if (url != null) { - if (url.contains("/Binary/")) { - return DataCaptureConfig.attachmentResolver?.run { - resolveBinaryResource(url)?.run { - val byteArray = Base64.decode(this.dataElement.valueAsString, Base64.DEFAULT) - BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) - } - } - } else if (url.startsWith("https") || url.startsWith("http")) { - return DataCaptureConfig.attachmentResolver?.run { this.resolveImageUrl(url) } + } else if (url != null && url.startsWith("https") || url.startsWith("http")) { + // Points to a Binary resource on a FHIR compliant server + return if (url.contains("/Binary/")) { + DataCaptureConfig.attachmentResolver?.run { resolveBinaryResource(url)?.getBitmap() } + } else { + DataCaptureConfig.attachmentResolver?.resolveImageUrl(url) } } + Timber.e(Throwable("Could not determine the Bitmap in Attachment $id")) return null } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 22aa41cf0d..d56d55d210 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -59,10 +59,15 @@ internal object QuestionnaireItemDisplayViewHolderFactory : View.VISIBLE } + itemImageView.setImageBitmap(null) + questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { it.fetchBitmap()?.run { - GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + GlobalScope.launch(Dispatchers.Main) { + itemImageView.visibility = View.VISIBLE + itemImageView.setImageBitmap(this@run) + } } } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 6b7a288e1a..47e8758054 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -74,10 +74,17 @@ internal abstract class QuestionnaireItemEditTextViewHolderDelegate( textQuestion.text = questionnaireItemViewItem.questionnaireItem.localizedText textInputEditText.setText(getText(questionnaireItemViewItem.singleAnswerOrNull)) + // The RecyclerView is recycling the ImageView therefore making them visible and recycling + // images from previous questions + itemImageView.setImageBitmap(null) + questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { it.fetchBitmap()?.run { - GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + GlobalScope.launch(Dispatchers.Main) { + itemImageView.visibility = View.VISIBLE + itemImageView.setImageBitmap(this@run) + } } } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 1e8e0fe45e..478c9af66a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -60,10 +60,15 @@ internal object QuestionnaireItemGroupViewHolderFactory : View.VISIBLE } + itemImageView.setImageBitmap(null) + questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { it.fetchBitmap()?.run { - GlobalScope.launch(Dispatchers.Main) { itemImageView.setImageBitmap(this@run) } + GlobalScope.launch(Dispatchers.Main) { + itemImageView.visibility = View.VISIBLE + itemImageView.setImageBitmap(this@run) + } } } } diff --git a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml index 9daf825d28..74c9b6870a 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml @@ -52,6 +52,7 @@ android:layout_gravity="center_horizontal" android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginVertical="@dimen/item_margin_vertical" + android:visibility="gone" /> diff --git a/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml b/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml index 62252857b0..d5bc02f5fd 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_edit_text_view.xml @@ -53,6 +53,7 @@ android:layout_height="wrap_content" android:layout_marginVertical="@dimen/item_margin_vertical" android:layout_marginHorizontal="@dimen/item_margin_horizontal" + android:visibility="gone" /> diff --git a/reference/src/main/assets/binary.json b/reference/src/main/assets/binary.json deleted file mode 100644 index db7d9b5399..0000000000 --- a/reference/src/main/assets/binary.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "resourceType": "Binary", - "id": "syringe-image", - "contentType": "image/png", - "data": "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" -} \ No newline at end of file diff --git a/reference/src/main/assets/new-patient-registration-paginated.json b/reference/src/main/assets/new-patient-registration-paginated.json index 1073fe4067..046f2e08bf 100644 --- a/reference/src/main/assets/new-patient-registration-paginated.json +++ b/reference/src/main/assets/new-patient-registration-paginated.json @@ -33,14 +33,6 @@ { "url": "content", "valueString": "Maelezo ya mteja" - }, - { - "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment" : { - "id": "syringe-image", - "contentType": "image/png", - "url": "https://mpng.subpng.com/20180124/gpw/kisspng-syringe-injection-cartoon-blue-syringe-5a68881609c163.27719744151680002204.jpg" - } } ], "url": "http://hl7.org/fhir/StructureDefinition/translation" @@ -60,14 +52,6 @@ "expression": "HumanName", "name": "humanName" } - }, - { - "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment" : { - "id": "syringe-image-2", - "contentType": "image/png", - "url": "https://fhir.labs.smartregister.org/fhir/Binary/syringe-image" - } } ], "item": [ @@ -80,14 +64,6 @@ "expression": "Patient.name.given", "name": "patientName" } - }, - { - "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment" : { - "id": "okImage", - "contentType": "image/png", - "data": "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" - } } ], "linkId": "PR-name-text", @@ -271,14 +247,6 @@ "expression": "ContactPoint", "name": "contactPoint" } - }, - { - "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment" : { - "id": "okImage", - "contentType": "image/png", - "data": "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" - } } ], "item": [ @@ -316,13 +284,6 @@ "expression": "Patient.telecom.value", "name": "patientTelecom" } - }, - { - "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment" : { - "id": "okImage", - "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Combotrans.svg/1200px-Combotrans.svg.png" - } } ], "linkId": "PR-telecom-value", diff --git a/reference/src/main/assets/screener-questionnaire.json b/reference/src/main/assets/screener-questionnaire.json index 3e07caa37c..8908e124db 100644 --- a/reference/src/main/assets/screener-questionnaire.json +++ b/reference/src/main/assets/screener-questionnaire.json @@ -1081,11 +1081,10 @@ "linkId": "5.1.0", "extension": [ { - "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", - "valueAttachment": { - "id": "okImage", + "url" : "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", + "valueAttachment" : { "contentType": "image/png", - "data": "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" + "url": "https://hapi.fhir.org/baseR4/Binary/android-fhir-thermometer-image" } } ] diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt index c5184b98f3..531e072ce0 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt @@ -32,12 +32,9 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView -import ca.uhn.fhir.context.FhirContext import com.google.android.fhir.FhirEngine import com.google.android.fhir.reference.PatientListViewModel.PatientListViewModelFactory import com.google.android.fhir.reference.databinding.FragmentPatientListBinding -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Binary class PatientListFragment : Fragment() { private lateinit var fhirEngine: FhirEngine @@ -131,13 +128,6 @@ class PatientListFragment : Fragment() { } setHasOptionsMenu(true) (activity as MainActivity).setDrawerEnabled(true) - - val binaryJson = - requireActivity().assets.open("binary.json").bufferedReader().use { it.readText() } - - val binaryResource = - FhirContext.forR4().newJsonParser().parseResource(Binary::class.java, binaryJson) - runBlocking { fhirEngine.update(binaryResource) } } override fun onDestroyView() { diff --git a/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt index 0a6204dea5..0e8c03f65b 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt @@ -19,8 +19,9 @@ package com.google.android.fhir.reference import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import ca.uhn.fhir.context.FhirContext import com.google.android.fhir.datacapture.AttachmentResolver -import com.google.android.fhir.reference.api.NormalService +import com.google.android.fhir.reference.api.HapiFhirService import org.hl7.fhir.r4.model.Binary /** Created by Ephraim Kigamba - nek.eam@gmail.com on 04-10-2021. */ @@ -33,12 +34,15 @@ class ReferenceAttachmentResolver(val context: Context) : AttachmentResolver { } override suspend fun resolveImageUrl(uri: String): Bitmap? { - return NormalService.create().fetchImage(uri).execute().run { - if (this.body() != null) { - BitmapFactory.decodeStream(this.body()?.byteStream()) - } else { - null + return HapiFhirService.create(FhirContext.forR4().newJsonParser()) + .fetchImage(uri) + .execute() + .run { + if (this.body() != null) { + BitmapFactory.decodeStream(this.body()?.byteStream()) + } else { + null + } } - } } } diff --git a/reference/src/main/java/com/google/android/fhir/reference/api/HapiFhirService.kt b/reference/src/main/java/com/google/android/fhir/reference/api/HapiFhirService.kt index 21a71f2886..6b151f2773 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/api/HapiFhirService.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/api/HapiFhirService.kt @@ -19,10 +19,12 @@ package com.google.android.fhir.reference.api import ca.uhn.fhir.parser.IParser import okhttp3.OkHttpClient import okhttp3.RequestBody +import okhttp3.ResponseBody import okhttp3.logging.HttpLoggingInterceptor import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.OperationOutcome import org.hl7.fhir.r4.model.Resource +import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Body @@ -52,6 +54,8 @@ interface HapiFhirService { @DELETE("{type}/{id}") suspend fun deleteResource(@Path("type") type: String, @Path("id") id: String): OperationOutcome + @GET fun fetchImage(@Url url: String): Call + companion object { const val BASE_URL = "https://hapi.fhir.org/baseR4/" diff --git a/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt b/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt deleted file mode 100644 index 6fb4437ba0..0000000000 --- a/reference/src/main/java/com/google/android/fhir/reference/api/NormalService.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.reference.api - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import okhttp3.OkHttpClient -import okhttp3.ResponseBody -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.GET -import retrofit2.http.Url - -/** Created by Ephraim Kigamba - nek.eam@gmail.com on 04-10-2021. */ - -/** hapi.fhir.org API communication via Retrofit */ -interface NormalService { - - @GET fun fetchImage(@Url url: String): Call - - companion object { - const val BASE_URL = "https://hapi.fhir.org/baseR4/" - - fun create(): NormalService { - val gson: Gson = GsonBuilder().setLenient().create() - - val logger = HttpLoggingInterceptor() - logger.level = HttpLoggingInterceptor.Level.BODY - - val client = OkHttpClient.Builder().addInterceptor(logger).build() - return Retrofit.Builder() - .baseUrl(BASE_URL) - .client(client) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build() - .create(NormalService::class.java) - } - } -} diff --git a/reference/src/main/java/com/google/android/fhir/reference/data/FhirPeriodicSyncWorker.kt b/reference/src/main/java/com/google/android/fhir/reference/data/FhirPeriodicSyncWorker.kt index 736e45a25e..19f646e8e7 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/data/FhirPeriodicSyncWorker.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/data/FhirPeriodicSyncWorker.kt @@ -27,7 +27,11 @@ import org.hl7.fhir.r4.model.ResourceType class FhirPeriodicSyncWorker(appContext: Context, workerParams: WorkerParameters) : FhirSyncWorker(appContext, workerParams) { - override fun getSyncData() = mapOf(ResourceType.Patient to mapOf("address-city" to "NAIROBI")) + override fun getSyncData() = + mapOf( + ResourceType.Patient to mapOf("address-city" to "NAIROBI"), + ResourceType.Binary to mapOf("_id" to "android-fhir-thermometer-image") + ) override fun getDataSource() = HapiFhirResourceDataSource(HapiFhirService.create(FhirContext.forR4().newJsonParser())) From 4a818db8023c6a9acb0557593a459a5dd1db18b8 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Fri, 8 Oct 2021 14:23:17 +0300 Subject: [PATCH 07/75] Fix build --- ...leLineViewHolderFactoryInstrumentedTest.kt | 35 ------------------- .../reference/ReferenceAttachmentResolver.kt | 1 - 2 files changed, 36 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 68707460f4..dd86ce2807 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -16,7 +16,6 @@ package com.google.android.fhir.datacapture.views -import android.util.Base64 import android.widget.FrameLayout import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper @@ -27,8 +26,6 @@ import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.material.textfield.TextInputEditText import com.google.common.truth.Truth.assertThat -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType @@ -170,36 +167,4 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { assertThat(questionnaireItemViewItem.questionnaireResponseItem.answer.size).isEqualTo(0) } - - @Test - @UiThreadTest - fun shouldShowImageWhenItemImageExtensionAvailable() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.QuestionnaireItemComponent().apply { - prefix = "" - extension = - listOf( - Extension().apply { - url = "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage" - setValue( - Attachment().apply { - id = "ok-image" - contentType = "image/png" - data = - Base64.decode( - "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", - Base64.DEFAULT - ) - } - ) - } - ) - }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).isVisible).isTrue() - } } diff --git a/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt index 0e8c03f65b..7aac21165c 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/ReferenceAttachmentResolver.kt @@ -24,7 +24,6 @@ import com.google.android.fhir.datacapture.AttachmentResolver import com.google.android.fhir.reference.api.HapiFhirService import org.hl7.fhir.r4.model.Binary -/** Created by Ephraim Kigamba - nek.eam@gmail.com on 04-10-2021. */ class ReferenceAttachmentResolver(val context: Context) : AttachmentResolver { override suspend fun resolveBinaryResource(uri: String): Binary? { From 0c84423f7a158e40247105f4fca2aa9eb55bbeb4 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Fri, 22 Oct 2021 13:15:30 +0300 Subject: [PATCH 08/75] Remove Timber dependency --- buildSrc/src/main/kotlin/Dependencies.kt | 2 -- datacapture/build.gradle.kts | 1 - .../fhir/datacapture/MoreQuestionnaireItemComponents.kt | 8 ++++---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 45f1b29b38..07a74bdb16 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -111,7 +111,6 @@ object Dependencies { const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}" const val truth = "com.google.truth:truth:${Versions.truth}" const val flexBox = "com.google.android.flexbox:flexbox:${Versions.flexBox}" - const val timber = "com.jakewharton.timber:timber:${Versions.timber}" object Versions { object Androidx { @@ -164,6 +163,5 @@ object Dependencies { const val junit = "4.13" const val mockitoKotlin = "3.2.0" const val robolectric = "4.5.1" - const val timber = "5.0.1" } } diff --git a/datacapture/build.gradle.kts b/datacapture/build.gradle.kts index e4f2e6a9a8..519a9bc2c6 100644 --- a/datacapture/build.gradle.kts +++ b/datacapture/build.gradle.kts @@ -97,7 +97,6 @@ dependencies { implementation(Dependencies.Lifecycle.viewModelKtx) implementation(Dependencies.material) implementation(Dependencies.flexBox) - implementation(Dependencies.timber) testImplementation(Dependencies.AndroidxTest.core) testImplementation(Dependencies.junit) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 80bdb17476..2bdce01b03 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Base64 +import android.util.Log import java.util.Locale import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.Binary @@ -27,7 +28,6 @@ import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType -import timber.log.Timber internal enum class ItemControlTypes( val extensionCode: String, @@ -198,7 +198,7 @@ private fun Binary.getBitmap(): Bitmap? { BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) } } else { - Timber.e(Throwable("Binary does not have a contentType image")) + Log.e("Binary", "Binary does not have a contentType image") null } } @@ -215,7 +215,7 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { if (isImage) { return BitmapFactory.decodeByteArray(data, 0, data.size) } - Timber.e(Throwable("Attachment is not of contentType image/**")) + Log.e("Attachment", "Attachment is not of contentType image/**") return null } else if (url != null && url.startsWith("https") || url.startsWith("http")) { // Points to a Binary resource on a FHIR compliant server @@ -226,6 +226,6 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { } } - Timber.e(Throwable("Could not determine the Bitmap in Attachment $id")) + Log.e("Attachment", "Could not determine the Bitmap in Attachment $id") return null } From 4a820ec010a741c5f81e340cdc283e7b4ab3ed9e Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Tue, 26 Oct 2021 17:58:15 +0300 Subject: [PATCH 09/75] Add tests for Binary & Attachment extension funcs --- .../MoreQuestionnaireItemComponents.kt | 2 +- .../MoreQuestionnaireItemComponentsTest.kt | 52 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 2bdce01b03..1f428d747c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -217,7 +217,7 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { } Log.e("Attachment", "Attachment is not of contentType image/**") return null - } else if (url != null && url.startsWith("https") || url.startsWith("http")) { + } else if (url != null && (url.startsWith("https") || url.startsWith("http"))) { // Points to a Binary resource on a FHIR compliant server return if (url.contains("/Binary/")) { DataCaptureConfig.attachmentResolver?.run { resolveBinaryResource(url)?.getBitmap() } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 36ec4b2110..9bba85ae65 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -387,6 +387,16 @@ class MoreQuestionnaireItemComponentsTest { assertThat(bitmap).isNull() } + @Test + fun `fetchBitmap() should return null when Attachment has no data and no url`() { + val attachment = Attachment() + val bitmap: Bitmap? + + runBlocking { bitmap = attachment.fetchBitmap() } + + assertThat(bitmap).isNull() + } + @Test fun `fetchBitmap() should return Bitmap when Attachment has data and correct contentType`() { val attachment = @@ -403,13 +413,31 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `isImage() should true when Attachment contentType is image`() { + fun `Attachment#isImage() should return true when Attachment contentType is image`() { val attachment = Attachment().apply { contentType = "image/png" } assertThat(attachment.isImage).isTrue() } @Test - fun `isImage() should false when Attachment contentType is not image`() { + fun `Attachment#isImage() should return false when Attachment contentType is not image`() { + val attachment = Attachment().apply { contentType = "document/pdf" } + assertThat(attachment.isImage).isFalse() + } + + @Test + fun `Attachment#isImage() should return false when Attachment does not have contentType`() { + val attachment = Attachment() + assertThat(attachment.isImage).isFalse() + } + + @Test + fun `Binary#getBitmap() should return null when contentType is not for an image`() { + val attachment = Attachment().apply { contentType = "document/pdf" } + assertThat(attachment.isImage).isFalse() + } + + @Test + fun `Binary#getBitmap() should return null when contentType is null`() { val attachment = Attachment().apply { contentType = "document/pdf" } assertThat(attachment.isImage).isFalse() } @@ -441,6 +469,16 @@ class MoreQuestionnaireItemComponentsTest { } } + @Test + fun `fetchBitmap() should return null when Attachment has Binary resource url but AttachmentResolver not configured`() { + val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } + val bitmap: Bitmap? + + runBlocking { bitmap = attachment.fetchBitmap() } + + assertThat(bitmap).isNull() + } + @Test fun `fetchBitmap() should return Bitmap and call AttachmentResolver#resolveImageUrl`() { val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } @@ -465,4 +503,14 @@ class MoreQuestionnaireItemComponentsTest { .resolveImageUrl("https://some-image-server.com/images/f0006.png") } } + + @Test + fun `fetchBitmap() should return null when Attachment has external url to image but AttachmentResolver is not configured`() { + val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } + + val resolvedBitmap: Bitmap? + runBlocking { resolvedBitmap = attachment.fetchBitmap() } + + assertThat(resolvedBitmap).isNull() + } } From d8ba75f1582d01cbe29e80c8424d9a376e6e344e Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Mon, 22 Nov 2021 16:16:13 +0300 Subject: [PATCH 10/75] Publicise Binary#isImage & #getBitamp - Add tests for Binary#isImage & getBitmap --- .../MoreQuestionnaireItemComponents.kt | 4 +-- .../MoreQuestionnaireItemComponentsTest.kt | 35 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 1f428d747c..d76ca83c8a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -189,10 +189,10 @@ val Attachment.isImage: Boolean get() = this.hasContentType() && contentType.startsWith("image") /** Whether the Binary has a [Binary.contentType] for an image */ -private fun Binary.isImage(): Boolean = this.hasContentType() && contentType.startsWith("image") +fun Binary.isImage(): Boolean = this.hasContentType() && contentType.startsWith("image") /** Decodes the Bitmap from the Base64 encoded string in [Bitmap.data] */ -private fun Binary.getBitmap(): Bitmap? { +fun Binary.getBitmap(): Bitmap? { return if (isImage()) { Base64.decode(this.dataElement.valueAsString, Base64.DEFAULT).let { byteArray -> BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 9bba85ae65..ca5fc3368d 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -376,7 +376,7 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return null when Attachment has data and incorrect contentType`() { + fun `Attachment#fetchBitmap() should return null when Attachment has data and incorrect contentType`() { val attachment = Attachment().apply { data = "some-byte".toByteArray(Charset.forName("UTF-8")) @@ -388,7 +388,7 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return null when Attachment has no data and no url`() { + fun `Attachment#fetchBitmap() should return null when Attachment has no data and no url`() { val attachment = Attachment() val bitmap: Bitmap? @@ -398,7 +398,7 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return Bitmap when Attachment has data and correct contentType`() { + fun `Attachment#fetchBitmap() should return Bitmap when Attachment has data and correct contentType`() { val attachment = Attachment().apply { data = @@ -432,18 +432,30 @@ class MoreQuestionnaireItemComponentsTest { @Test fun `Binary#getBitmap() should return null when contentType is not for an image`() { - val attachment = Attachment().apply { contentType = "document/pdf" } - assertThat(attachment.isImage).isFalse() + val binary = Binary().apply { contentType = "document/pdf" } + assertThat(binary.getBitmap()).isNull() } @Test fun `Binary#getBitmap() should return null when contentType is null`() { - val attachment = Attachment().apply { contentType = "document/pdf" } - assertThat(attachment.isImage).isFalse() + val binary = Binary() + assertThat(binary.getBitmap()).isNull() + } + + @Test + fun `Binary#isImage() should return false when contentType is not for an image`() { + val binary = Binary().apply { contentType = "document/pdf" } + assertThat(binary.isImage()).isFalse() + } + + @Test + fun `Binary#isImage() should return false when contentType is null`() { + val binary = Binary() + assertThat(binary.isImage()).isFalse() } @Test - fun `fetchBitmap() should return Bitmap and call AttachmentResolver#resolveBinaryResource`() { + fun `Attachment#fetchBitmap() should return Bitmap and call AttachmentResolver#resolveBinaryResource`() { val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } val binary = Binary().apply { @@ -470,7 +482,7 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return null when Attachment has Binary resource url but AttachmentResolver not configured`() { + fun `Attachment#fetchBitmap() should return null when Attachment has Binary resource url but AttachmentResolver not configured`() { val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } val bitmap: Bitmap? @@ -480,7 +492,7 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return Bitmap and call AttachmentResolver#resolveImageUrl`() { + fun `Attachment#fetchBitmap() should return Bitmap and call AttachmentResolver#resolveImageUrl`() { val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } val byteArray = Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) @@ -505,9 +517,10 @@ class MoreQuestionnaireItemComponentsTest { } @Test - fun `fetchBitmap() should return null when Attachment has external url to image but AttachmentResolver is not configured`() { + fun `Attachment#fetchBitmap() should return null when Attachment has external url to image but AttachmentResolver is not configured`() { val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } + DataCaptureConfig.attachmentResolver = null val resolvedBitmap: Bitmap? runBlocking { resolvedBitmap = attachment.fetchBitmap() } From 13368987b087a9a15d848210f308d3510f8da6c9 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Mon, 22 Nov 2021 17:33:31 +0300 Subject: [PATCH 11/75] Add itemImage extension tests - Add tests for Binary#getBitmap and Binary#isImage - Add tests for image display in QuestionnaireItemEditTextSingleLineViewHolderFactory, QuestionnaireItemGroupViewHolderFactory and QuestionnaireItemDisplayViewHolderFactory - Add instrumented tests for Attachment#fetchBitmap --- ...tionnaireItemComponentsInstrumentedTest.kt | 119 ++++++++++++++++++ ...isplayViewHolderFactoryInstrumentedTest.kt | 54 ++++++++ ...leLineViewHolderFactoryInstrumentedTest.kt | 55 ++++++++ ...mGroupViewHolderFactoryInstrumentedTest.kt | 54 ++++++++ ...estionnaireItemDisplayViewHolderFactory.kt | 2 + 5 files changed, 284 insertions(+) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt new file mode 100644 index 0000000000..516c55e288 --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import java.nio.charset.Charset +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Binary +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MoreQuestionnaireItemComponentsInstrumentedTest { + + @Test + fun fetchBitmap_shouldReturnNull_whenAttachmentHasDataAndIncorrectContentType() { + val attachment = + Attachment().apply { + data = "some-byte".toByteArray(Charset.forName("UTF-8")) + contentType = "document/pdf" + } + val bitmap: Bitmap? + runBlocking { bitmap = attachment.fetchBitmap() } + assertThat(bitmap).isNull() + } + + @Test + fun fetchBitmap_shouldReturnBitmap_whenAttachmentHasDataAndCorrectContentType() { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val bitmap: Bitmap? + runBlocking { bitmap = attachment.fetchBitmap() } + assertThat(bitmap).isNotNull() + } + + @Test + fun isImage_shouldTrue_whenAttachmentContentTypeIsImage() { + val attachment = Attachment().apply { contentType = "image/png" } + assertThat(attachment.isImage).isTrue() + } + + @Test + fun isImage_shouldFalseWhenAttachmentContentTypeIsNotImage() { + val attachment = Attachment().apply { contentType = "document/pdf" } + assertThat(attachment.isImage).isFalse() + } + + @Test + fun fetchBitmap_shouldReturnBitmapAndCallAttachmentResolverResolveBinaryResource() { + val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } + DataCaptureConfig.attachmentResolver = TestAttachmentResolver() + + val bitmap: Bitmap? + runBlocking { bitmap = attachment.fetchBitmap() } + + assertThat(bitmap).isNotNull() + } + + @Test + fun fetchBitmap_shouldReturnBitmapAndCallAttachmentResolverResolveImageUrl() { + val attachment = Attachment().apply { url = "https://some-image-server.com/images/f0006.png" } + + val byteArray = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + val expectedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + + DataCaptureConfig.attachmentResolver = TestAttachmentResolver(expectedBitmap) + + val resolvedBitmap: Bitmap? + runBlocking { resolvedBitmap = attachment.fetchBitmap() } + + assertThat(resolvedBitmap).isEqualTo(expectedBitmap) + } + + class TestAttachmentResolver(var testBitmap: Bitmap? = null) : AttachmentResolver { + + override suspend fun resolveBinaryResource(uri: String): Binary? { + return if (uri == "https://hapi.fhir.org/Binary/f006") { + Binary().apply { + contentType = "image/png" + data = + Base64.decode( + "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + Base64.DEFAULT + ) + } + } else null + } + + override suspend fun resolveImageUrl(uri: String): Bitmap? { + return if (uri == "https://some-image-server.com/images/f0006.png") { + testBitmap + } else null + } + } +} diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index ae2ce2e516..e7b27221f8 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -16,8 +16,10 @@ package com.google.android.fhir.datacapture.views +import android.util.Base64 import android.view.View import android.widget.FrameLayout +import android.widget.ImageView import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible @@ -25,6 +27,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before @@ -110,4 +116,52 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { assertThat(viewHolder.itemView.findViewById(R.id.text_view).visibility) .isEqualTo(View.GONE) } + + @Test + fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 30516329b0..0eb5d6a358 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -16,7 +16,10 @@ package com.google.android.fhir.datacapture.views +import android.util.Base64 +import android.view.View import android.widget.FrameLayout +import android.widget.ImageView import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible @@ -27,6 +30,10 @@ import com.google.android.fhir.datacapture.R import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.IntegerType import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -219,4 +226,52 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { assertThat(viewHolder.itemView.findViewById(R.id.textInputLayout).error) .isEqualTo("The minimum number of characters that are permitted in the answer is: 10") } + + @Test + fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 1aff4036a0..1b3bed87e4 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -16,8 +16,10 @@ package com.google.android.fhir.datacapture.views +import android.util.Base64 import android.view.View import android.widget.FrameLayout +import android.widget.ImageView import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible @@ -26,7 +28,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before @@ -152,4 +158,52 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { assertThat(viewHolder.itemView.findViewById(R.id.group_header).error).isNull() } + + @Test + fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index d56d55d210..5972abaddc 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -67,6 +67,8 @@ internal object QuestionnaireItemDisplayViewHolderFactory : GlobalScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) + + // throw RuntimeException("At last! This has run") } } } From 8f4d122a1bb714ca11d32decfbff7062867e12bc Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Thu, 6 Jan 2022 17:19:13 +0300 Subject: [PATCH 12/75] Fix datacapture module instrumented tests --- .../MoreQuestionnaireItemComponentsInstrumentedTest.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt index fe8ce749c1..c956e13d1b 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt @@ -19,7 +19,9 @@ package com.google.android.fhir.datacapture import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Base64 +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication import com.google.common.truth.Truth.assertThat import java.nio.charset.Charset import kotlinx.coroutines.runBlocking @@ -71,7 +73,9 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { @Test fun fetchBitmap_shouldReturnBitmapAndCallAttachmentResolverResolveBinaryResource() { val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } - DataCaptureConfig.attachmentResolver = TestAttachmentResolver() + ApplicationProvider.getApplicationContext() + .getDataCaptureConfig() + .attachmentResolver = TestAttachmentResolver() val bitmap: Bitmap? runBlocking { bitmap = attachment.fetchBitmap() } @@ -87,7 +91,9 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) val expectedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) - DataCaptureConfig.attachmentResolver = TestAttachmentResolver(expectedBitmap) + ApplicationProvider.getApplicationContext() + .getDataCaptureConfig() + .attachmentResolver = TestAttachmentResolver(expectedBitmap) val resolvedBitmap: Bitmap? runBlocking { resolvedBitmap = attachment.fetchBitmap() } From b08a3a26534b6d5f1a72497417607d35443453fe Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Thu, 6 Jan 2022 23:25:10 +0300 Subject: [PATCH 13/75] Fix datacapture module instrumented tests --- .../src/androidTest/AndroidManifest.xml | 1 + .../datacapture/DataCaptureTestApplication.kt | 32 +++++++++++++++++++ ...tionnaireItemComponentsInstrumentedTest.kt | 11 ++++--- .../fhir/datacapture/DataCaptureConfig.kt | 10 ++++-- .../MoreQuestionnaireItemComponents.kt | 8 +++-- ...estionnaireItemDisplayViewHolderFactory.kt | 2 +- ...stionnaireItemEditTextViewHolderFactory.kt | 2 +- ...QuestionnaireItemGroupViewHolderFactory.kt | 2 +- .../MoreQuestionnaireItemComponentsTest.kt | 30 ++++++++++------- .../testing/DataCaptureTestApplication.kt | 10 ++++-- 10 files changed, 81 insertions(+), 27 deletions(-) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt diff --git a/datacapture/src/androidTest/AndroidManifest.xml b/datacapture/src/androidTest/AndroidManifest.xml index 4d2f89ab21..9d354ab04b 100644 --- a/datacapture/src/androidTest/AndroidManifest.xml +++ b/datacapture/src/androidTest/AndroidManifest.xml @@ -21,6 +21,7 @@ diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt new file mode 100644 index 0000000000..7ebe3d91fc --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture + +import android.app.Application + +/** Application class when you want to test the DataCaptureConfig.Provider */ +internal class DataCaptureTestApplication : Application(), DataCaptureConfig.Provider { + var dataCaptureConfiguration: DataCaptureConfig? = null + + override fun getDataCaptureConfig(): DataCaptureConfig { + if (dataCaptureConfiguration == null) { + dataCaptureConfiguration = DataCaptureConfig() + } + + return dataCaptureConfiguration!! + } +} diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt index c956e13d1b..4af6d394e6 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt @@ -21,7 +21,6 @@ import android.graphics.BitmapFactory import android.util.Base64 import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication import com.google.common.truth.Truth.assertThat import java.nio.charset.Charset import kotlinx.coroutines.runBlocking @@ -41,7 +40,7 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { contentType = "document/pdf" } val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNull() } @@ -54,7 +53,7 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { contentType = "image/png" } val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNotNull() } @@ -78,7 +77,7 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { .attachmentResolver = TestAttachmentResolver() val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNotNull() } @@ -96,7 +95,9 @@ class MoreQuestionnaireItemComponentsInstrumentedTest { .attachmentResolver = TestAttachmentResolver(expectedBitmap) val resolvedBitmap: Bitmap? - runBlocking { resolvedBitmap = attachment.fetchBitmap() } + runBlocking { + resolvedBitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) + } assertThat(resolvedBitmap).isEqualTo(expectedBitmap) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt index f6daaa071e..873693c4ea 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt @@ -47,15 +47,19 @@ data class DataCaptureConfig( * should try to include the smallest [NpmPackage] possible that contains only the resources * needed by [StructureMap]s used by the client app. */ - var npmPackage: NpmPackage? = null + var npmPackage: NpmPackage? = null, + + /** + * Used to resolve/process [org.hl7.fhir.r4.model.Attachment] to it's binary equivalent. The attachment's can + * either have the data or a URL + */ + var attachmentResolver: AttachmentResolver? = null ) { internal val simpleWorkerContext: SimpleWorkerContext by lazy { if (npmPackage == null) SimpleWorkerContext() else SimpleWorkerContext.fromPackage(npmPackage) } - var attachmentResolver: AttachmentResolver? = null - /** * A class that can provide the [DataCaptureConfig] for the Structured Data Capture Library. To do * this, implement the {@link DataCaptureConfig.Provider} interface on your [Application] class. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 5044858ff5..0b84fc8d14 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -16,6 +16,7 @@ package com.google.android.fhir.datacapture +import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Base64 @@ -209,7 +210,7 @@ fun Binary.getBitmap(): Bitmap? { * defined in the url or externally hosted image. Inline Base64 encoded image requires to have * contentType starting with image */ -suspend fun Attachment.fetchBitmap(): Bitmap? { +suspend fun Attachment.fetchBitmap(context: Context): Bitmap? { // Attachment's with data inline need the contentType property // Conversion to Bitmap should only be made if the contentType is image if (data != null) { @@ -220,10 +221,11 @@ suspend fun Attachment.fetchBitmap(): Bitmap? { return null } else if (url != null && (url.startsWith("https") || url.startsWith("http"))) { // Points to a Binary resource on a FHIR compliant server + val attachmentResolver = DataCapture.getConfiguration(context).attachmentResolver return if (url.contains("/Binary/")) { - DataCaptureConfig.attachmentResolver?.run { resolveBinaryResource(url)?.getBitmap() } + attachmentResolver?.run { resolveBinaryResource(url)?.getBitmap() } } else { - DataCaptureConfig.attachmentResolver?.resolveImageUrl(url) + attachmentResolver?.resolveImageUrl(url) } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 15327a5352..e790903155 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -63,7 +63,7 @@ internal object QuestionnaireItemDisplayViewHolderFactory : questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { - it.fetchBitmap()?.run { + it.fetchBitmap(itemImageView.context)?.run { GlobalScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 08fb6785d7..84d34991ba 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -104,7 +104,7 @@ internal abstract class QuestionnaireItemEditTextViewHolderDelegate( questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { - it.fetchBitmap()?.run { + it.fetchBitmap(itemImageView.context)?.run { GlobalScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index f746d11491..097774350e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -64,7 +64,7 @@ internal object QuestionnaireItemGroupViewHolderFactory : questionnaireItemViewItem.questionnaireItem.itemImage?.let { GlobalScope.launch { - it.fetchBitmap()?.run { + it.fetchBitmap(itemImageView.context)?.run { GlobalScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index e2297a7aa5..65e0b7021f 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -36,6 +36,7 @@ import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.utils.ToolingExtensions +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito @@ -43,11 +44,17 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import org.robolectric.util.ReflectionHelpers @RunWith(RobolectricTestRunner::class) -@Config(sdk = [Build.VERSION_CODES.P]) +@Config(sdk = [Build.VERSION_CODES.P], application = DataCaptureTestApplication::class) class MoreQuestionnaireItemComponentsTest { + @Before + fun setUp() { + ReflectionHelpers.setStaticField(DataCapture::class.java, "configuration", null) + } + @Test fun itemControl_shouldReturnItemControlCodeDropDown() { val questionnaireItem = @@ -385,7 +392,7 @@ class MoreQuestionnaireItemComponentsTest { contentType = "document/pdf" } val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNull() } @@ -394,7 +401,7 @@ class MoreQuestionnaireItemComponentsTest { val attachment = Attachment() val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNull() } @@ -410,7 +417,7 @@ class MoreQuestionnaireItemComponentsTest { contentType = "image/png" } val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNotNull() } @@ -478,7 +485,7 @@ class MoreQuestionnaireItemComponentsTest { .getDataCaptureConfig() .attachmentResolver = attachmentResolver - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNotNull() runBlocking { @@ -491,7 +498,7 @@ class MoreQuestionnaireItemComponentsTest { val attachment = Attachment().apply { url = "https://hapi.fhir.org/Binary/f006" } val bitmap: Bitmap? - runBlocking { bitmap = attachment.fetchBitmap() } + runBlocking { bitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) } assertThat(bitmap).isNull() } @@ -509,12 +516,11 @@ class MoreQuestionnaireItemComponentsTest { ) .doReturn(bitmap) } - ApplicationProvider.getApplicationContext() - .getDataCaptureConfig() - .attachmentResolver = attachmentResolver + var context = ApplicationProvider.getApplicationContext() + context.getDataCaptureConfig().attachmentResolver = attachmentResolver val resolvedBitmap: Bitmap? - runBlocking { resolvedBitmap = attachment.fetchBitmap() } + runBlocking { resolvedBitmap = attachment.fetchBitmap(context) } assertThat(resolvedBitmap).isEqualTo(bitmap) runBlocking { @@ -531,7 +537,9 @@ class MoreQuestionnaireItemComponentsTest { .getDataCaptureConfig() .attachmentResolver = null val resolvedBitmap: Bitmap? - runBlocking { resolvedBitmap = attachment.fetchBitmap() } + runBlocking { + resolvedBitmap = attachment.fetchBitmap(ApplicationProvider.getApplicationContext()) + } assertThat(resolvedBitmap).isNull() } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt index 1d56aabd95..83e40cdf00 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt @@ -20,8 +20,14 @@ import android.app.Application import com.google.android.fhir.datacapture.DataCaptureConfig /** Application class when you want to test the DataCaptureConfig.Provider */ -internal class DataCaptureTestApplication : Application(), DataCaptureConfig.Provider { +class DataCaptureTestApplication : Application(), DataCaptureConfig.Provider { var dataCaptureConfiguration: DataCaptureConfig? = null - override fun getDataCaptureConfig() = dataCaptureConfiguration ?: DataCaptureConfig() + override fun getDataCaptureConfig(): DataCaptureConfig { + if (dataCaptureConfiguration == null) { + dataCaptureConfiguration = DataCaptureConfig() + } + + return dataCaptureConfiguration!! + } } From 442fe468ade825b68956f54038f709ef4d46ec17 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Fri, 1 Apr 2022 17:16:53 +0300 Subject: [PATCH 14/75] Fix demo app build --- .../main/java/com/google/android/fhir/demo/FhirApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt b/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt index 99bd13e6c2..8344863780 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt @@ -49,7 +49,7 @@ class FhirApplication : Application(), DataCaptureConfig.Provider { dataCaptureConfiguration = DataCaptureConfig().apply { - attachmentResolver = ReferenceAttachmentResolver(this as Context) + attachmentResolver = ReferenceAttachmentResolver(this@FhirApplication as Context) } } From 24e3aa919cddda9aa5d9c0e533546c312ce2d237 Mon Sep 17 00:00:00 2001 From: Ephraim Kigamba Date: Tue, 10 May 2022 11:22:37 +0300 Subject: [PATCH 15/75] Code reformatting with spotlessApply --- ...uestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 2 -- .../views/QuestionnaireItemDisplayViewHolderFactory.kt | 3 +-- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 3 +-- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 2 +- .../src/main/res/layout/questionnaire_item_display_view.xml | 2 +- .../main/res/layout/questionnaire_item_group_header_view.xml | 2 +- .../fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt | 2 +- 7 files changed, 6 insertions(+), 10 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 86e499deb9..1ae3b12b61 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -20,9 +20,7 @@ import android.util.Base64 import android.view.View import android.widget.FrameLayout import android.widget.ImageView -import android.widget.TextView import androidx.appcompat.view.ContextThemeWrapper -import androidx.core.view.isVisible import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index a3aa74305b..e432fc1ee8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -18,9 +18,9 @@ package com.google.android.fhir.datacapture.views import android.view.View import android.widget.ImageView +import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage -import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.validation.ValidationResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -37,7 +37,6 @@ internal object QuestionnaireItemDisplayViewHolderFactory : override fun init(itemView: View) { header = itemView.findViewById(R.id.header) itemImageView = itemView.findViewById(R.id.itemImage) - } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 57cc0ce2a8..409ddaab0d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -23,11 +23,10 @@ import android.view.View.FOCUS_DOWN import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.ImageView -import android.widget.TextView import androidx.core.widget.doAfterTextChanged +import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage -import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.localizedFlyoverSpanned import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index e8c0da54f7..522a9fb5a8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -19,9 +19,9 @@ package com.google.android.fhir.datacapture.views import android.view.View import android.widget.ImageView import android.widget.TextView +import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage -import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import kotlinx.coroutines.Dispatchers diff --git a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml index 887b4f8b37..6cba2674e3 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml @@ -37,6 +37,6 @@ android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginVertical="@dimen/item_margin_vertical" android:visibility="gone" - /> + /> diff --git a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml index e781e3be72..0d5550dc79 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml @@ -36,7 +36,7 @@ android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginVertical="@dimen/item_margin_vertical" android:visibility="gone" - /> + /> Date: Wed, 11 May 2022 11:20:39 +0500 Subject: [PATCH 16/75] Implement calculated-expression extension --- .../calculated_expression_questionnaire.json | 24 +++++++ .../fhir/catalog/ComponentListViewModel.kt | 5 ++ catalog/src/main/res/values/strings.xml | 1 + .../MoreQuestionnaireItemComponents.kt | 10 +++ .../datacapture/QuestionnaireViewModel.kt | 31 +++++++++ .../datacapture/mapping/ResourceMapper.kt | 3 +- .../datacapture/QuestionnaireViewModelTest.kt | 66 +++++++++++++++++++ 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 catalog/src/main/assets/calculated_expression_questionnaire.json diff --git a/catalog/src/main/assets/calculated_expression_questionnaire.json b/catalog/src/main/assets/calculated_expression_questionnaire.json new file mode 100644 index 0000000000..921d7fc9be --- /dev/null +++ b/catalog/src/main/assets/calculated_expression_questionnaire.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Questionnaire", + "item": [ + { + "linkId": "a-birthdate", + "text": "Birth Date", + "type": "date" + }, + { + "linkId": "a-age-years", + "text": "Age years", + "type": "integer", + "extension": [ + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression", + "valueExpression": { + "language": "text/fhirpath", + "expression": "today().toString().substring(0, 4).toInteger() - %resource.repeat(item).where(linkId='a-birthdate').answer.value.toString().substring(0, 4).toInteger()" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index c5f89ac281..a5cfdd0762 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -77,5 +77,10 @@ class ComponentListViewModel(application: Application, private val state: SavedS ), SLIDER(R.drawable.ic_slider, R.string.component_name_slider, "slider_questionnaire.json"), IMAGE(R.drawable.ic_image, R.string.component_name_image, ""), + CALCULATED_EXPRESSION( + R.drawable.ic_unitoptions, + R.string.component_name_calculated_expression, + "calculated_expression_questionnaire.json" + ), } } diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml index 9f33de96d6..773e124759 100644 --- a/catalog/src/main/res/values/strings.xml +++ b/catalog/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Slider Dropdown Image + Calculated Expression Default Paginated Review diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index f09777d743..c5f4e4fca1 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -22,6 +22,7 @@ import com.google.android.fhir.getLocalizedText import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeType import org.hl7.fhir.r4.model.CodeableConcept +import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -53,6 +54,15 @@ internal const val EXTENSION_ITEM_CONTROL_SYSTEM = "http://hl7.org/fhir/question internal const val EXTENSION_HIDDEN_URL = "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden" +internal const val EXTENSION_CALCULATED_EXPRESSION_URL = + "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression" + +internal val Questionnaire.QuestionnaireItemComponent.calculatedExpression: Expression? + get() = + this.getExtensionByUrl(EXTENSION_CALCULATED_EXPRESSION_URL)?.let { + it.castToExpression(it.value) + } + // Item control code, or null internal val Questionnaire.QuestionnaireItemComponent.itemControl: ItemControlTypes? get() { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 5c4c2f3439..431c47050e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -18,6 +18,7 @@ package com.google.android.fhir.datacapture import android.app.Application import android.net.Uri +import androidx.annotation.VisibleForTesting import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope @@ -31,12 +32,14 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.ResourceType import org.hl7.fhir.r4.model.ValueSet +import org.hl7.fhir.r4.utils.FHIRPathEngine import timber.log.Timber internal class QuestionnaireViewModel(application: Application, state: SavedStateHandle) : @@ -44,6 +47,11 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat /** The current questionnaire as questions are being answered. */ internal val questionnaire: Questionnaire + private val fhirPathEngine: FHIRPathEngine = + with(FhirContext.forCached(FhirVersionEnum.R4)) { + FHIRPathEngine(HapiWorkerContext(this, this.validationSupport)) + } + init { questionnaire = when { @@ -113,6 +121,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * Callback function to update the UI which takes the linkId of the question whose answer(s) has * been changed. */ + @com.google.common.annotations.VisibleForTesting private val questionnaireResponseItemChangedCallback: (String) -> Unit = { linkId -> linkIdToQuestionnaireItemMap[linkId]?.let { questionnaireItem -> if (questionnaireItem.hasNestedItemsWithinAnswers) { @@ -126,6 +135,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } } + modificationCount.value += 1 } @@ -173,6 +183,21 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat ) ) + fun runCalculatedExpressions() { + val calculableItems = + linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } + calculableItems.forEach { questionnaireItem -> + linkIdToQuestionnaireResponseItemMap[questionnaireItem.key]?.let { questionnaireResponseItem + -> + val expression = questionnaireItem.value.calculatedExpression!!.expression + fhirPathEngine.evaluate(null, questionnaireResponse, null, null, expression).run { + questionnaireResponseItem.answerFirstRep.value = + this.firstOrNull()?.let { it.castToType(it) } + } + } + } + } + @PublishedApi internal suspend fun resolveAnswerValueSet( uri: String @@ -254,6 +279,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat questionnaireResponseItemList: List, pagination: QuestionnairePagination?, ): QuestionnaireState { + + runCalculatedExpressions() + // TODO(kmost): validate pages before switching between next/prev pages var responseIndex = 0 val items: List = @@ -317,6 +345,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat .items } .toList() + return QuestionnaireState(items = items, pagination = pagination) } @@ -355,6 +384,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * when first opening this questionnaire. Otherwise, returns `null`. */ private fun Questionnaire.getInitialPagination(): QuestionnairePagination? { + runCalculatedExpressions() + val usesPagination = item.any { item -> item.extension.any { extension -> diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt index de2bad893e..975e0ab644 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt @@ -18,7 +18,6 @@ package com.google.android.fhir.datacapture.mapping import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.context.FhirVersionEnum -import ca.uhn.fhir.context.support.DefaultProfileValidationSupport import com.google.android.fhir.datacapture.DataCapture import com.google.android.fhir.datacapture.createQuestionnaireResponseItem import com.google.android.fhir.datacapture.targetStructureMap @@ -72,7 +71,7 @@ object ResourceMapper { private val fhirPathEngine: FHIRPathEngine = with(FhirContext.forCached(FhirVersionEnum.R4)) { - FHIRPathEngine(HapiWorkerContext(this, DefaultProfileValidationSupport(this))) + FHIRPathEngine(HapiWorkerContext(this, this.validationSupport)) } /** diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index 19a783ce18..ffe805f68e 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -26,9 +26,12 @@ import ca.uhn.fhir.parser.IParser import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_JSON_STRING import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_JSON_URI import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_RESPONSE_JSON_STRING +import com.google.android.fhir.datacapture.common.datatype.asStringValue import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication import com.google.common.truth.Truth.assertThat import java.io.File +import java.util.Calendar +import java.util.Date import kotlin.test.assertFailsWith import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -36,6 +39,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.DateType +import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.IntegerType import org.hl7.fhir.r4.model.Questionnaire @@ -1422,6 +1427,67 @@ class QuestionnaireViewModelTest(private val questionnaireSource: QuestionnaireS .isEqualTo("parent-question") } + @Test + fun questionnaireItem_calculatedExpressionExtension_shouldCalculateValue() = runBlocking { + val questionnaire = + Questionnaire().apply { + id = "a-questionnaire" + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-birthdate" + type = Questionnaire.QuestionnaireItemType.DATE + addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + DateType(Date()).apply { add(Calendar.YEAR, -2) } + ) + ) + } + ) + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-age-years" + type = Questionnaire.QuestionnaireItemType.INTEGER + addExtension().apply { + url = EXTENSION_CALCULATED_EXPRESSION_URL + setValue( + Expression().apply { + this.language = "text/fhirpath" + this.expression = + "today().toString().substring(0, 4).toInteger() - %resource.repeat(item).where(linkId='a-birthdate').answer.value.toString().substring(0, 4).toInteger()" + } + ) + } + } + ) + } + val serializedQuestionnaire = printer.encodeResourceToString(questionnaire) + state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, serializedQuestionnaire) + + val viewModel = QuestionnaireViewModel(context, state) + + assertThat( + viewModel + .getQuestionnaireResponse() + .item + .single { it.linkId == "a-birthdate" } + .answerFirstRep + .value + .asStringValue() + ) + .isEqualTo(DateType(Date()).apply { add(Calendar.YEAR, -2) }.asStringValue()) + + assertThat( + viewModel + .getQuestionnaireResponse() + .item + .single { it.linkId == "a-age-years" } + .answerFirstRep + .valueIntegerType + .value + ) + .isEqualTo(2) + } + private fun createQuestionnaireViewModel( questionnaire: Questionnaire, response: QuestionnaireResponse? = null From b569b8183e31e613416bfe94ab9a4d9698103c4b Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 11:25:02 +0500 Subject: [PATCH 17/75] Fix form value update bug --- .../calculated_expression_questionnaire.json | 5 +- .../fhir/catalog/ComponentListViewModel.kt | 6 +-- catalog/src/main/res/values/strings.xml | 4 +- .../fhir/datacapture/QuestionnaireFragment.kt | 6 +++ .../datacapture/QuestionnaireViewModel.kt | 50 +++++++++++++++---- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/catalog/src/main/assets/calculated_expression_questionnaire.json b/catalog/src/main/assets/calculated_expression_questionnaire.json index 921d7fc9be..ef82a90a14 100644 --- a/catalog/src/main/assets/calculated_expression_questionnaire.json +++ b/catalog/src/main/assets/calculated_expression_questionnaire.json @@ -4,7 +4,10 @@ { "linkId": "a-birthdate", "text": "Birth Date", - "type": "date" + "type": "date", + "initial": [{ + "valueDate": "2021-01-01" + }] }, { "linkId": "a-age-years", diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index a5cfdd0762..2eb45c198b 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -78,9 +78,9 @@ class ComponentListViewModel(application: Application, private val state: SavedS SLIDER(R.drawable.ic_slider, R.string.component_name_slider, "slider_questionnaire.json"), IMAGE(R.drawable.ic_image, R.string.component_name_image, ""), CALCULATED_EXPRESSION( - R.drawable.ic_unitoptions, - R.string.component_name_calculated_expression, - "calculated_expression_questionnaire.json" + R.drawable.ic_unitoptions, + R.string.component_name_calculated_expression, + "calculated_expression_questionnaire.json" ), } } diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml index 773e124759..3a2f5b8d4b 100644 --- a/catalog/src/main/res/values/strings.xml +++ b/catalog/src/main/res/values/strings.xml @@ -29,7 +29,9 @@ Slider Dropdown Image - Calculated Expression + Calculated Expression Default Paginated Review diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt index 9d914e987c..fe1f40c1a7 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt @@ -82,6 +82,12 @@ open class QuestionnaireFragment : Fragment() { } } } + + viewLifecycleOwner.lifecycleScope.launchWhenCreated { + viewModel.questionnaireItemValueStateFlow.collect { index -> + if (index > -1 && !recyclerView.isComputingLayout) adapter.notifyItemChanged(index) + } + } } /** diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 431c47050e..cd74e2c625 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -18,7 +18,6 @@ package com.google.android.fhir.datacapture import android.app.Application import android.net.Uri -import androidx.annotation.VisibleForTesting import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope @@ -28,10 +27,15 @@ import com.google.android.fhir.datacapture.enablement.EnablementEvaluator import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator.checkQuestionnaireResponse import com.google.android.fhir.datacapture.views.QuestionnaireItemViewItem import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -121,7 +125,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * Callback function to update the UI which takes the linkId of the question whose answer(s) has * been changed. */ - @com.google.common.annotations.VisibleForTesting private val questionnaireResponseItemChangedCallback: (String) -> Unit = { linkId -> linkIdToQuestionnaireItemMap[linkId]?.let { questionnaireItem -> if (questionnaireItem.hasNestedItemsWithinAnswers) { @@ -136,6 +139,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } + runCalculatedExpressions() + modificationCount.value += 1 } @@ -183,7 +188,13 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat ) ) - fun runCalculatedExpressions() { + /** + * Notify the updated value index to notify about the item update for UI (adapter item changed) + */ + private val _questionnaireItemValueStateFlow = MutableSharedFlow() + internal val questionnaireItemValueStateFlow = _questionnaireItemValueStateFlow.asSharedFlow() + + private fun runCalculatedExpressions() { val calculableItems = linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } calculableItems.forEach { questionnaireItem -> @@ -191,8 +202,30 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat -> val expression = questionnaireItem.value.calculatedExpression!!.expression fhirPathEngine.evaluate(null, questionnaireResponse, null, null, expression).run { - questionnaireResponseItem.answerFirstRep.value = - this.firstOrNull()?.let { it.castToType(it) } + with(this.firstOrNull()) { + // update only if answer has changed, otherwise app can stuck in loop to keep updating + // changed items + val evaluatedAnswer = this?.castToType(this) + val currentAnswer = + if (questionnaireResponseItem.hasAnswer()) + questionnaireResponseItem.answerFirstRep.value + else null + if (!(evaluatedAnswer == null && currentAnswer == null) && + evaluatedAnswer?.equalsDeep(currentAnswer) != true + ) { + questionnaireResponseItem.answerFirstRep.value = + evaluatedAnswer?.let { it.castToType(it) } + + // notify UI to update it value i.e. notify item changed to adapter + viewModelScope.launch { + questionnaireStateFlow.collectLatest { + it.items + .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } + .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } + } + } + } + } } } } @@ -279,9 +312,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat questionnaireResponseItemList: List, pagination: QuestionnairePagination?, ): QuestionnaireState { - - runCalculatedExpressions() - // TODO(kmost): validate pages before switching between next/prev pages var responseIndex = 0 val items: List = @@ -317,7 +347,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat QuestionnaireItemViewItem( questionnaireItem, questionnaireResponseItem, - { resolveAnswerValueSet(it) } + { resolveAnswerValueSet(it) }, ) { questionnaireResponseItemChangedCallback(questionnaireItem.linkId) } ) + getQuestionnaireState( @@ -384,8 +414,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * when first opening this questionnaire. Otherwise, returns `null`. */ private fun Questionnaire.getInitialPagination(): QuestionnairePagination? { - runCalculatedExpressions() - val usesPagination = item.any { item -> item.extension.any { extension -> From d4721907ba585618714631f0883c2061670f37e1 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 15:20:49 +0500 Subject: [PATCH 18/75] Detect cyclic dependency | fix on init value loading --- .../datacapture/QuestionnaireViewModel.kt | 33 +++++++++-- .../datacapture/QuestionnaireViewModelTest.kt | 57 +++++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index cd74e2c625..ba5da0aad1 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -210,6 +210,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat if (questionnaireResponseItem.hasAnswer()) questionnaireResponseItem.answerFirstRep.value else null + if (!(evaluatedAnswer == null && currentAnswer == null) && evaluatedAnswer?.equalsDeep(currentAnswer) != true ) { @@ -218,11 +219,12 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat // notify UI to update it value i.e. notify item changed to adapter viewModelScope.launch { - questionnaireStateFlow.collectLatest { - it.items - .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } - .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } - } + if (modificationCount.value > 0) + questionnaireStateFlow.collectLatest { + it.items + .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } + .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } + } } } } @@ -231,6 +233,24 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } + fun detectCalculatedExpressionCyclicDependency() { + val calculableItems = + linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } + calculableItems.forEach { current -> + val currentExpression = current.value.calculatedExpression!!.expression + val otherDependent = + calculableItems.values.find { + it.calculatedExpression!!.expression.contains("'${current.key}'") + } + // if any calculable expression depends on this item and this item is referring to the + // dependent item in its own expression then raise error + if (otherDependent != null && currentExpression.contains("'${otherDependent.linkId}'")) + throw IllegalStateException( + "${current.key} and ${otherDependent.linkId} have cyclic dependency in calculated-expression extension" + ) + } + } + @PublishedApi internal suspend fun resolveAnswerValueSet( uri: String @@ -414,6 +434,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * when first opening this questionnaire. Otherwise, returns `null`. */ private fun Questionnaire.getInitialPagination(): QuestionnairePagination? { + detectCalculatedExpressionCyclicDependency() + runCalculatedExpressions() + val usesPagination = item.any { item -> item.extension.any { extension -> diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index ffe805f68e..050f1e474d 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -47,6 +47,7 @@ import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.model.ValueSet +import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -1488,6 +1489,62 @@ class QuestionnaireViewModelTest(private val questionnaireSource: QuestionnaireS .isEqualTo(2) } + @Test + fun questionnaireItem_calculatedExpressionExtension_shouldDetectCyclicDependency() = runBlocking { + val questionnaire = + Questionnaire().apply { + id = "a-questionnaire" + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-birthdate" + type = Questionnaire.QuestionnaireItemType.DATE + addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + DateType(Date()).apply { add(Calendar.YEAR, -2) } + ) + ) + addExtension().apply { + url = EXTENSION_CALCULATED_EXPRESSION_URL + setValue( + Expression().apply { + this.language = "text/fhirpath" + this.expression = + "%resource.repeat(item).where(linkId='a-birthdate').answer.value.toString()" + } + ) + } + } + ) + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-age-years" + type = Questionnaire.QuestionnaireItemType.INTEGER + addExtension().apply { + url = EXTENSION_CALCULATED_EXPRESSION_URL + setValue( + Expression().apply { + this.language = "text/fhirpath" + this.expression = + "today().toString().substring(0, 4).toInteger() - %resource.repeat(item).where(linkId='a-birthdate').answer.value.toString().substring(0, 4).toInteger()" + } + ) + } + } + ) + } + val serializedQuestionnaire = printer.encodeResourceToString(questionnaire) + state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, serializedQuestionnaire) + + val exception = + Assert.assertThrows(null, IllegalStateException::class.java) { + QuestionnaireViewModel(context, state) + } + assertThat(exception.message) + .isNotEqualTo( + "a-birthdate and a-age-years have cyclic dependency in calculated-expression extension" + ) + } + private fun createQuestionnaireViewModel( questionnaire: Questionnaire, response: QuestionnaireResponse? = null From 5536e96ab3b1383fd0a6d1ecc9c5a8d872f58e28 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 15:27:56 +0500 Subject: [PATCH 19/75] Fix merge conflict --- .../android/fhir/datacapture/QuestionnaireViewModelTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index 3a9d9ec2ea..e2214a8af8 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -27,6 +27,7 @@ import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_JSON_URI import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_RESPONSE_JSON_STRING import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_RESPONSE_JSON_URI +import com.google.android.fhir.datacapture.common.datatype.asStringValue import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication import com.google.common.truth.Truth.assertThat import java.io.File From bce9fd96550fe9f97b535876f403cef28a3108b1 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 20:09:04 +0500 Subject: [PATCH 20/75] Make birthdate age dependent | Handle and fix quantity values --- .../calculated_expression_questionnaire.json | 28 ++++++++++----- .../MoreQuestionnaireItemComponents.kt | 7 +++- ...ionnaireItemDatePickerViewHolderFactory.kt | 6 +++- ...reItemEditTextQuantityViewHolderFactory.kt | 10 +++++- .../datacapture/QuestionnaireViewModelTest.kt | 35 ++++++++++--------- 5 files changed, 57 insertions(+), 29 deletions(-) diff --git a/catalog/src/main/assets/calculated_expression_questionnaire.json b/catalog/src/main/assets/calculated_expression_questionnaire.json index ef82a90a14..3e72d87668 100644 --- a/catalog/src/main/assets/calculated_expression_questionnaire.json +++ b/catalog/src/main/assets/calculated_expression_questionnaire.json @@ -5,23 +5,33 @@ "linkId": "a-birthdate", "text": "Birth Date", "type": "date", - "initial": [{ - "valueDate": "2021-01-01" - }] - }, - { - "linkId": "a-age-years", - "text": "Age years", - "type": "integer", "extension": [ { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "today().toString().substring(0, 4).toInteger() - %resource.repeat(item).where(linkId='a-birthdate').answer.value.toString().substring(0, 4).toInteger()" + "expression": "%resource.repeat(item).where(linkId='a-age-years' and answer.empty().not()).select(today() - answer.value)" } } ] + }, + { + "linkId": "a-age-years", + "text": "Age years", + "hint": "Input age to automatically calculate birthdate in case exact birthdate is not known", + "type": "quantity", + "initial": [{ + "valueQuantity": { + "unit": "months", + "system": "http://unitsofmeasure.org", + "code": "months" + } + }] + }, + { + "linkId": "a-age-note", + "text": "Input age to automatically calculate birthdate in case exact birthdate is not known", + "type": "display" } ] } \ No newline at end of file diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index effe7b1fd5..e93a864e07 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -244,7 +244,12 @@ val Questionnaire.QuestionnaireItemComponent.enableWhenExpression: Expression? */ private fun Questionnaire.QuestionnaireItemComponent.createQuestionnaireResponseItemAnswers(): MutableList? { - if (initial.isEmpty()) { + // https://build.fhir.org/ig/HL7/sdc/behavior.html#initial + // quantity given as initial without value is for unit reference purpose only. Answer conversion + // not needed + if (initial.isEmpty() || + (initialFirstRep.hasValueQuantity() && initialFirstRep.valueQuantity.value == null) + ) { return null } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 76d9f75d3c..b47c54d476 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -82,7 +82,11 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : header.bind(questionnaireItemViewItem.questionnaireItem) textInputEditText.setText( - questionnaireItemViewItem.singleAnswerOrNull?.valueDateType?.localDate?.localizedString + questionnaireItemViewItem.singleAnswerOrNull + ?.takeIf { it.hasValue() } + ?.valueDateType + ?.localDate + ?.localizedString ) questionnaireItemViewItem.questionnaireItem.entryFormat?.let { textInputLayout.helperText = it diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index 9c3eeb78eb..1df529139d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -35,8 +35,16 @@ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : override fun getValue( text: String ): QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent? { + // https://build.fhir.org/ig/HL7/sdc/behavior.html#initial + // read default unit from initial, as ideally quantity must specify a unit return text.toDoubleOrNull()?.let { - QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(Quantity(it)) + val quantity = + with(questionnaireItemViewItem.questionnaireItem) { + if (this.hasInitial() && this.initialFirstRep.valueQuantity.hasCode()) + Quantity.fromUcum(text, this.initialFirstRep.valueQuantity.code) + else Quantity(it) + } + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(quantity) } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index e2214a8af8..d94311b505 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -1441,29 +1441,29 @@ class QuestionnaireViewModelTest( Questionnaire.QuestionnaireItemComponent().apply { linkId = "a-birthdate" type = Questionnaire.QuestionnaireItemType.DATE - addInitial( - Questionnaire.QuestionnaireItemInitialComponent( - DateType(Date()).apply { add(Calendar.YEAR, -2) } - ) - ) - } - ) - addItem( - Questionnaire.QuestionnaireItemComponent().apply { - linkId = "a-age-years" - type = Questionnaire.QuestionnaireItemType.INTEGER addExtension().apply { url = EXTENSION_CALCULATED_EXPRESSION_URL setValue( Expression().apply { this.language = "text/fhirpath" this.expression = - "today().toString().substring(0, 4).toInteger() - %resource.repeat(item).where(linkId='a-birthdate').answer.value.toString().substring(0, 4).toInteger()" + "%resource.repeat(item).where(linkId='a-age-years' and answer.empty().not()).select(today() - answer.value)" } ) } } ) + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-age-years" + type = Questionnaire.QuestionnaireItemType.QUANTITY + /* addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity.fromUcum("1", "year") + ) + )*/ + } + ) } val serializedQuestionnaire = printer.encodeResourceToString(questionnaire) state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, serializedQuestionnaire) @@ -1479,7 +1479,7 @@ class QuestionnaireViewModelTest( .value .asStringValue() ) - .isEqualTo(DateType(Date()).apply { add(Calendar.YEAR, -2) }.asStringValue()) + .isEqualTo(DateType(Date()).apply { add(Calendar.YEAR, -1) }.asStringValue()) assertThat( viewModel @@ -1487,10 +1487,11 @@ class QuestionnaireViewModelTest( .item .single { it.linkId == "a-age-years" } .answerFirstRep - .valueIntegerType + .valueQuantity .value + .toString() ) - .isEqualTo(2) + .isEqualTo("1") } @Test @@ -1513,7 +1514,7 @@ class QuestionnaireViewModelTest( Expression().apply { this.language = "text/fhirpath" this.expression = - "%resource.repeat(item).where(linkId='a-birthdate').answer.value.toString()" + "%resource.repeat(item).where(linkId='a-age-years' and answer.empty().not()).select(today() - answer.value)" } ) } @@ -1544,7 +1545,7 @@ class QuestionnaireViewModelTest( QuestionnaireViewModel(context, state) } assertThat(exception.message) - .isNotEqualTo( + .isEqualTo( "a-birthdate and a-age-years have cyclic dependency in calculated-expression extension" ) } From 777489b49d0883617d0ff8c19b10ba8cbeb332fa Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 20:22:03 +0500 Subject: [PATCH 21/75] Fix failing test --- .../fhir/datacapture/QuestionnaireViewModelTest.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index d94311b505..b0c37d56e3 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -44,6 +44,7 @@ import org.hl7.fhir.r4.model.DateType import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.IntegerType +import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType @@ -1457,11 +1458,9 @@ class QuestionnaireViewModelTest( Questionnaire.QuestionnaireItemComponent().apply { linkId = "a-age-years" type = Questionnaire.QuestionnaireItemType.QUANTITY - /* addInitial( - Questionnaire.QuestionnaireItemInitialComponent( - Quantity.fromUcum("1", "year") - ) - )*/ + addInitial( + Questionnaire.QuestionnaireItemInitialComponent(Quantity.fromUcum("1", "year")) + ) } ) } From 0ee4c27e8ac3e53f002af422dbcd480e664f4187 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Thu, 12 May 2022 23:17:03 +0500 Subject: [PATCH 22/75] quantity viewholder delegate test covergae --- .../datacapture/QuestionnaireViewModel.kt | 2 +- ...reItemEditTextQuantityViewHolderFactory.kt | 9 +++- .../QuestionnaireItemViewHolderFactory.kt | 2 +- ...emEditTextQuantityViewHolderFactoryTest.kt | 41 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index e8cecd1e99..5fe795069d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -378,7 +378,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat QuestionnaireItemViewItem( questionnaireItem, questionnaireResponseItem, - { resolveAnswerValueSet(it) }, + { resolveAnswerValueSet(it) } ) { questionnaireResponseItemChangedCallback(questionnaireItem.linkId) } ) + getQuestionnaireState( diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index 1df529139d..90186f7b7c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -17,6 +17,7 @@ package com.google.android.fhir.datacapture.views import android.text.InputType +import java.math.BigDecimal import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -41,7 +42,13 @@ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : val quantity = with(questionnaireItemViewItem.questionnaireItem) { if (this.hasInitial() && this.initialFirstRep.valueQuantity.hasCode()) - Quantity.fromUcum(text, this.initialFirstRep.valueQuantity.code) + this.initialFirstRep.valueQuantity.let { initial -> + Quantity().apply { + this.value = BigDecimal(text) + this.code = initial.code + this.system = initial.system + } + } else Quantity(it) } QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(quantity) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt index 09c5e05d41..f8a44fb4aa 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt @@ -52,7 +52,7 @@ abstract class QuestionnaireItemViewHolderFactory(@LayoutRes val resId: Int) { */ open class QuestionnaireItemViewHolder( itemView: View, - private val delegate: QuestionnaireItemViewHolderDelegate + internal val delegate: QuestionnaireItemViewHolderDelegate ) : RecyclerView.ViewHolder(itemView) { init { delegate.init(itemView) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt index 34ae56352e..a68c8f6f95 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt @@ -197,4 +197,45 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryTest { ) .isFalse() } + + @Test + fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + required = true + addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity().apply { + code = "months" + system = "http://unitofmeasure.com" + } + ) + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + val value = + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity + assertThat(value.code).isEqualTo("months") + assertThat(value.system).isEqualTo("http://unitofmeasure.com") + assertThat(value.value).isEqualTo(BigDecimal(22)) + } + + fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { required = true }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + val value = + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity + assertThat(value.code).isNull() + assertThat(value.system).isNull() + assertThat(value.value).isEqualTo(BigDecimal(22)) + } } From 3fe03fe472e58da01265b9b6ddab3b0bb3946ad2 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 13 May 2022 15:13:18 +0500 Subject: [PATCH 23/75] Test coverage for update flow --- .../fhir/datacapture/QuestionnaireFragment.kt | 2 +- .../datacapture/QuestionnaireViewModel.kt | 1 + ...ionnaireItemDatePickerViewHolderFactory.kt | 3 +- .../datacapture/QuestionnaireViewModelTest.kt | 71 +++++++++++++++++-- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt index 1ce7f3db79..1394ed790f 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt @@ -85,7 +85,7 @@ open class QuestionnaireFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launchWhenCreated { viewModel.questionnaireItemValueStateFlow.collect { index -> - if (index > -1 && !recyclerView.isComputingLayout) adapter.notifyItemChanged(index) + if (!recyclerView.isComputingLayout) adapter.notifyItemChanged(index) } } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 5fe795069d..e2a082d5be 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext import org.hl7.fhir.r4.model.CodeableConcept diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index b47c54d476..3cd4878f67 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -81,8 +81,7 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) - textInputEditText.setText( - questionnaireItemViewItem.singleAnswerOrNull + textInputEditText.setText(questionnaireItemViewItem.singleAnswerOrNull ?.takeIf { it.hasValue() } ?.valueDateType ?.localDate diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index b0c37d56e3..69e855756b 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -1434,7 +1434,7 @@ class QuestionnaireViewModelTest( } @Test - fun questionnaireItem_calculatedExpressionExtension_shouldCalculateValue() = runBlocking { + fun questionnaireItem_calculatedExpressionExtension_shouldCalculateValue_onStart() = runBlocking { val questionnaire = Questionnaire().apply { id = "a-questionnaire" @@ -1464,10 +1464,8 @@ class QuestionnaireViewModelTest( } ) } - val serializedQuestionnaire = printer.encodeResourceToString(questionnaire) - state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, serializedQuestionnaire) - val viewModel = QuestionnaireViewModel(context, state) + val viewModel = createQuestionnaireViewModel(questionnaire) assertThat( viewModel @@ -1493,6 +1491,67 @@ class QuestionnaireViewModelTest( .isEqualTo("1") } + @Test + fun questionnaireItem_calculatedExpressionExtension_shouldCalculateValue_onChange() = + runBlocking { + val questionnaire = + Questionnaire().apply { + id = "a-questionnaire" + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-birthdate" + type = Questionnaire.QuestionnaireItemType.DATE + addExtension().apply { + url = EXTENSION_CALCULATED_EXPRESSION_URL + setValue( + Expression().apply { + this.language = "text/fhirpath" + this.expression = + "%resource.repeat(item).where(linkId='a-age-years' and answer.empty().not()).select(today() - answer.value)" + } + ) + } + } + ) + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-age-years" + type = Questionnaire.QuestionnaireItemType.INTEGER + } + ) + } + + val viewModel = createQuestionnaireViewModel(questionnaire) + + val current = + viewModel + .getQuestionnaireItemViewItemList() + .first { it.questionnaireResponseItem.linkId == "a-birthdate" } + .apply { this.questionnaireResponseItemChangedCallback() } + + assertThat(current.questionnaireResponseItem.answer).isEmpty() + + viewModel + .getQuestionnaireItemViewItemList() + .first { it.questionnaireResponseItem.linkId == "a-age-years" } + .apply { + questionnaireResponseItem.addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + this.value = Quantity.fromUcum("2", "years") + } + ) + + this.questionnaireResponseItemChangedCallback() + } + + val updated = + viewModel.getQuestionnaireItemViewItemList().first { + it.questionnaireResponseItem.linkId == "a-birthdate" + } + assertThat(updated.questionnaireResponseItem.answer.first().valueDateType.valueAsString) + .isEqualTo(DateType(Date()).apply { add(Calendar.YEAR, -2) }.valueAsString) + } + @Test fun questionnaireItem_calculatedExpressionExtension_shouldDetectCyclicDependency() = runBlocking { val questionnaire = @@ -1536,12 +1595,10 @@ class QuestionnaireViewModelTest( } ) } - val serializedQuestionnaire = printer.encodeResourceToString(questionnaire) - state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, serializedQuestionnaire) val exception = Assert.assertThrows(null, IllegalStateException::class.java) { - QuestionnaireViewModel(context, state) + createQuestionnaireViewModel(questionnaire) } assertThat(exception.message) .isEqualTo( From 8ae31610e8a68fd197fe47b2e136117430999fa4 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 13 May 2022 15:20:42 +0500 Subject: [PATCH 24/75] spotless fix --- .../views/QuestionnaireItemDatePickerViewHolderFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 3cd4878f67..b47c54d476 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -81,7 +81,8 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) - textInputEditText.setText(questionnaireItemViewItem.singleAnswerOrNull + textInputEditText.setText( + questionnaireItemViewItem.singleAnswerOrNull ?.takeIf { it.hasValue() } ?.valueDateType ?.localDate From e56bd98a8fb16c73ef7ca067d327a70e2e7b2648 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 13 May 2022 15:20:42 +0500 Subject: [PATCH 25/75] spotless fix | re-run ci --- .../views/QuestionnaireItemDatePickerViewHolderFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 3cd4878f67..b47c54d476 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -81,7 +81,8 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) - textInputEditText.setText(questionnaireItemViewItem.singleAnswerOrNull + textInputEditText.setText( + questionnaireItemViewItem.singleAnswerOrNull ?.takeIf { it.hasValue() } ?.valueDateType ?.localDate From 219dd9e464e745a64d554660a57ab800ec2d05ea Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 20 May 2022 04:26:34 +0500 Subject: [PATCH 26/75] Test covergae for questionnaire fragment --- .../datacapture/QuestionnaireFragmentTest.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt new file mode 100644 index 0000000000..dc8d37c549 --- /dev/null +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture + +import android.os.Build +import androidx.core.os.bundleOf +import androidx.fragment.app.testing.launchFragment +import androidx.fragment.app.testing.withFragment +import androidx.lifecycle.Lifecycle +import ca.uhn.fhir.context.FhirContext +import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_JSON_STRING +import com.google.common.truth.Truth.assertThat +import org.hl7.fhir.r4.model.Questionnaire +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.P]) +class QuestionnaireFragmentTest { + + @Test + fun testFragment_ShouldBeAbleToBuildQuestionnaireResponse() { + val questionnaire = + Questionnaire().apply { + id = "a-questionnaire" + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-link-id" + type = Questionnaire.QuestionnaireItemType.BOOLEAN + } + ) + } + val questionnaireJson = + FhirContext.forR4().newJsonParser().encodeResourceToString(questionnaire) + val scenario = + launchFragment( + bundleOf(EXTRA_QUESTIONNAIRE_JSON_STRING to questionnaireJson) + ) + scenario.moveToState(Lifecycle.State.RESUMED) + scenario.withFragment { + assertThat(this.getQuestionnaireResponse()).isNotNull() + assertThat(this.getQuestionnaireResponse().item.any { it.linkId == "a-link-id" }).isTrue() + } + } +} From 5fff50d9ffb04df87b544d89c1e0f85dcb4e77d8 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 20 May 2022 13:34:06 +0500 Subject: [PATCH 27/75] test coverage for quantity types --- .../MoreQuestionnaireItemComponentsTest.kt | 58 +++++++++++++++++++ ...aireItemDatePickerViewHolderFactoryTest.kt | 18 ++++++ 2 files changed, 76 insertions(+) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index e5fa88846f..f988c2af88 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture import android.os.Build import com.google.android.fhir.datacapture.mapping.ITEM_INITIAL_EXPRESSION_URL import com.google.common.truth.Truth.assertThat +import java.math.BigDecimal import java.util.Locale import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeType @@ -27,6 +28,7 @@ import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.Enumeration import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.utils.ToolingExtensions @@ -806,6 +808,62 @@ class MoreQuestionnaireItemComponentsTest { .isEqualTo(true) } + @Test + fun createQuestionResponseWithQuantityType_ShouldNotSetAnswer_WithValueEmpty() { + val question = + Questionnaire.QuestionnaireItemComponent( + StringType("age"), + Enumeration( + Questionnaire.QuestionnaireItemTypeEnumFactory(), + Questionnaire.QuestionnaireItemType.QUANTITY + ) + ) + .apply { + initial = + listOf( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity().apply { + code = "months" + system = "http://unitofmeausre.org" + } + ) + ) + } + + val questionResponse = question.createQuestionnaireResponseItem() + + assertThat(questionResponse.answer).isEmpty() + } + + @Test + fun createQuestionResponseWithQuantityType_ShouldSetAnswer() { + val question = + Questionnaire.QuestionnaireItemComponent( + StringType("age"), + Enumeration( + Questionnaire.QuestionnaireItemTypeEnumFactory(), + Questionnaire.QuestionnaireItemType.QUANTITY + ) + ) + .apply { + initial = + listOf( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity().apply { + code = "months" + system = "http://unitofmeausre.org" + value = BigDecimal("1") + } + ) + ) + } + + val questionResponse = question.createQuestionnaireResponseItem() + val answer = questionResponse.answerFirstRep.value as Quantity + assertThat(answer.value).isEqualTo(BigDecimal(1)) + assertThat(answer.code).isEqualTo("months") + } + @Test fun entryFormat_missingFormat_shouldReturnNull() { val questionnaireItem = diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt index 473e8b385b..ec8d81c0ac 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt @@ -65,6 +65,24 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { .isEqualTo("") } + @Test + fun shouldSetEmptyDateInput_WhenDateFieldInitialized_AndDateIsNull() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(DateType()) + ) + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() + ) + .isEqualTo("") + } + @Test fun shouldSetDateInput_localeUs() { setLocale(Locale.US) From 23ed2e3810eb15a1f380b75462ed9a0f9001b0d4 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 20 May 2022 14:58:00 +0500 Subject: [PATCH 28/75] questionnaire fragment test with launchInFragmentContainer --- .../android/fhir/datacapture/QuestionnaireFragmentTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt index dc8d37c549..f7ea076010 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt @@ -18,7 +18,7 @@ package com.google.android.fhir.datacapture import android.os.Build import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragment +import androidx.fragment.app.testing.launchFragmentInContainer import androidx.fragment.app.testing.withFragment import androidx.lifecycle.Lifecycle import ca.uhn.fhir.context.FhirContext @@ -49,7 +49,7 @@ class QuestionnaireFragmentTest { val questionnaireJson = FhirContext.forR4().newJsonParser().encodeResourceToString(questionnaire) val scenario = - launchFragment( + launchFragmentInContainer( bundleOf(EXTRA_QUESTIONNAIRE_JSON_STRING to questionnaireJson) ) scenario.moveToState(Lifecycle.State.RESUMED) From a52078c747359f81fed7a36b0aa9667c34b408ca Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sat, 21 May 2022 18:53:10 +0700 Subject: [PATCH 29/75] Move tryUnwrapContext() to utilities --- ...naireItemBarCodeReaderViewHolderFactory.kt | 2 +- .../fhir/datacapture/utilities/MoreContext.kt | 42 +++++++++++++++++++ ...ionnaireItemDatePickerViewHolderFactory.kt | 25 +---------- ...aireItemDateTimePickerViewHolderFactory.kt | 1 + ...nnaireItemDialogSelectViewHolderFactory.kt | 1 + ...estionnaireItemDisplayViewHolderFactory.kt | 3 +- 6 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt index 052c718537..de05b33c1b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt @@ -25,11 +25,11 @@ import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.contrib.views.barcode.mlkit.md.LiveBarcodeScanningFragment import com.google.android.fhir.datacapture.localizedPrefixSpanned import com.google.android.fhir.datacapture.localizedTextSpanned +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.views.QuestionnaireItemViewHolderDelegate import com.google.android.fhir.datacapture.views.QuestionnaireItemViewHolderFactory import com.google.android.fhir.datacapture.views.QuestionnaireItemViewItem -import com.google.android.fhir.datacapture.views.tryUnwrapContext import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt new file mode 100644 index 0000000000..0c4af923a2 --- /dev/null +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.utilities + +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper + +/** + * Returns the [AppCompatActivity] if there exists one wrapped inside [ContextThemeWrapper] s, or + * `null` otherwise. + * + * This function is inspired by the function with the same name in `AppCompateDelegateImpl`. See + * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=1615 + * + * TODO: find a more robust way to do this as it is not guaranteed that the activity is an + * AppCompatActivity. + */ +internal fun Context.tryUnwrapContext(): AppCompatActivity? { + var context = this + while (true) { + when (context) { + is AppCompatActivity -> return context + is ContextThemeWrapper -> context = context.baseContext + else -> return null + } + } +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 76d9f75d3c..a745af8f86 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -17,14 +17,12 @@ package com.google.android.fhir.datacapture.views import android.annotation.SuppressLint -import android.content.Context import android.text.InputType import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.view.ContextThemeWrapper import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.entryFormat import com.google.android.fhir.datacapture.utilities.localizedString +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import com.google.android.material.datepicker.MaterialDatePicker @@ -121,27 +119,6 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : internal const val TAG = "date-picker" internal val ZONE_ID_UTC = ZoneId.of("UTC") -/** - * Returns the [AppCompatActivity] if there exists one wrapped inside [ContextThemeWrapper] s, or - * `null` otherwise. - * - * This function is inspired by the function with the same name in `AppCompateDelegateImpl`. See - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=1615 - * - * TODO: find a more robust way to do this as it is not guaranteed that the activity is an - * AppCompatActivity. - */ -internal fun Context.tryUnwrapContext(): AppCompatActivity? { - var context = this - while (true) { - when (context) { - is AppCompatActivity -> return context - is ContextThemeWrapper -> context = context.baseContext - else -> return null - } - } -} - internal val DateType.localDate get() = LocalDate.of( diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt index c714e6e2f2..f51702ec91 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt @@ -27,6 +27,7 @@ import com.google.android.fhir.datacapture.utilities.localizedDateString import com.google.android.fhir.datacapture.utilities.localizedString import com.google.android.fhir.datacapture.utilities.toLocalizedString import com.google.android.fhir.datacapture.utilities.toLocalizedTimeString +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import com.google.android.material.datepicker.MaterialDatePicker diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt index 55400a1caf..dbab7b37b0 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt @@ -29,6 +29,7 @@ import com.google.android.fhir.datacapture.common.datatype.asStringValue import com.google.android.fhir.datacapture.displayString import com.google.android.fhir.datacapture.itemControl import com.google.android.fhir.datacapture.localizedTextSpanned +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import com.google.android.material.textfield.TextInputLayout diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index e432fc1ee8..3931e1ea85 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -18,12 +18,13 @@ package com.google.android.fhir.datacapture.views import android.view.View import android.widget.ImageView +import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch internal object QuestionnaireItemDisplayViewHolderFactory : From f7958ff6da48e08235f091a986d3ed5d21e87178 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sat, 21 May 2022 19:08:39 +0700 Subject: [PATCH 30/75] Use lifeCycleScope --- .../views/QuestionnaireItemDisplayViewHolderFactory.kt | 5 +++-- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 8 +++++--- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 8 +++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 3931e1ea85..6e154d5f7b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -46,9 +46,10 @@ internal object QuestionnaireItemDisplayViewHolderFactory : itemImageView.setImageBitmap(null) questionnaireItemViewItem.questionnaireItem.itemImage?.let { - GlobalScope.launch { + val activity = itemImageView.context.tryUnwrapContext()!! + activity.lifecycleScope.launch { it.fetchBitmap(itemImageView.context)?.run { - GlobalScope.launch(Dispatchers.Main) { + activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 409ddaab0d..4a6cbe2479 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -24,16 +24,17 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.ImageView import androidx.core.widget.doAfterTextChanged +import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.localizedFlyoverSpanned +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -94,9 +95,10 @@ internal abstract class QuestionnaireItemEditTextViewHolderDelegate( itemImageView.setImageBitmap(null) questionnaireItemViewItem.questionnaireItem.itemImage?.let { - GlobalScope.launch { + val activity = itemImageView.context.tryUnwrapContext()!! + activity.lifecycleScope.launch { it.fetchBitmap(itemImageView.context)?.run { - GlobalScope.launch(Dispatchers.Main) { + activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 522a9fb5a8..c00b972492 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -19,13 +19,14 @@ package com.google.android.fhir.datacapture.views import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch internal object QuestionnaireItemGroupViewHolderFactory : @@ -49,9 +50,10 @@ internal object QuestionnaireItemGroupViewHolderFactory : itemImageView.setImageBitmap(null) questionnaireItemViewItem.questionnaireItem.itemImage?.let { - GlobalScope.launch { + val activity = itemImageView.context.tryUnwrapContext()!! + activity.lifecycleScope.launch { it.fetchBitmap(itemImageView.context)?.run { - GlobalScope.launch(Dispatchers.Main) { + activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) } From fef0052f7c20d6422774760ed25a303983239292 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 25 May 2022 08:36:35 +0700 Subject: [PATCH 31/75] Fix failing test --- ...leLineViewHolderFactoryInstrumentedTest.kt | 120 ++++++++++++++++++ ...EditTextSingleLineViewHolderFactoryTest.kt | 55 -------- 2 files changed, 120 insertions(+), 55 deletions(-) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt new file mode 100644 index 0000000000..7990939369 --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.util.Base64 +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { + + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setup() { + activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } + viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) + setTestLayout(viewHolder.itemView) + } + + @Test + fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.getScenario().onActivity { activity -> action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryTest.kt index f0bda6830c..49ad8ce28a 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryTest.kt @@ -16,19 +16,12 @@ package com.google.android.fhir.datacapture.views -import android.util.Base64 -import android.view.View import android.widget.FrameLayout -import android.widget.ImageView import android.widget.TextView import com.google.android.fhir.datacapture.R import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.IntegerType import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -205,52 +198,4 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryTest { ) .isFalse() } - - @Test - fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) - } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) - .isEqualTo(View.GONE) - } } From fde21ab0db6e81ef49090c04ae9e4cb0e33861d1 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 25 May 2022 12:59:19 +0700 Subject: [PATCH 32/75] Fix failing test --- ...mGroupViewHolderFactoryInstrumentedTest.kt | 120 ++++++++++++++++++ ...tionnaireItemGroupViewHolderFactoryTest.kt | 54 -------- 2 files changed, 120 insertions(+), 54 deletions(-) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt new file mode 100644 index 0000000000..31903d339f --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.util.Base64 +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { + + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setup() { + activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } + viewHolder = QuestionnaireItemGroupViewHolderFactory.create(parent) + setTestLayout(viewHolder.itemView) + } + + @Test + fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.getScenario().onActivity { activity -> action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryTest.kt index 593a8768af..e0ee3efeee 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryTest.kt @@ -16,19 +16,13 @@ package com.google.android.fhir.datacapture.views -import android.util.Base64 import android.view.View import android.widget.FrameLayout -import android.widget.ImageView import android.widget.TextView import com.google.android.fhir.datacapture.R import com.google.common.truth.Truth.assertThat import kotlin.test.assertTrue -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.Coding -import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Test @@ -174,52 +168,4 @@ class QuestionnaireItemGroupViewHolderFactoryTest { ) .isEqualTo(View.GONE) } - - @Test - fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) - } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) - .isEqualTo(View.GONE) - } } From b26b103ba4f35f5d21d05cef1e87c4073c90b12c Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 25 May 2022 20:58:52 +0700 Subject: [PATCH 33/75] Fix failing test --- .../google/android/fhir/demo/ReferenceAttachmentResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt index 65045dd8af..2be8e8160a 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt @@ -19,13 +19,14 @@ package com.google.android.fhir.demo import android.content.Context import android.graphics.Bitmap import com.google.android.fhir.datacapture.AttachmentResolver +import com.google.android.fhir.get import org.hl7.fhir.r4.model.Binary class ReferenceAttachmentResolver(val context: Context) : AttachmentResolver { override suspend fun resolveBinaryResource(uri: String): Binary? { return uri.substringAfter("Binary/").substringBefore("/").run { - FhirApplication.fhirEngine(context).load(Binary::class.java, this) + FhirApplication.fhirEngine(context).get(this) } } From 35e7541c67ca01e13ff41a7201fd934535fd4433 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Thu, 26 May 2022 07:10:03 +0700 Subject: [PATCH 34/75] Fix failing test --- ...isplayViewHolderFactoryInstrumentedTest.kt | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 1ae3b12b61..9fc414b331 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -20,10 +20,11 @@ import android.util.Base64 import android.view.View import android.widget.FrameLayout import android.widget.ImageView -import androidx.appcompat.view.ContextThemeWrapper +import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -32,24 +33,26 @@ import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper + + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + private lateinit var parent: FrameLayout private lateinit var viewHolder: QuestionnaireItemViewHolder @Before - fun setUp() { - context = - ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemDisplayViewHolderFactory.create(parent) + fun setup() { + activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } + viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) + setTestLayout(viewHolder.itemView) } @Test @@ -68,12 +71,14 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) ) } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } delay(1000) @@ -87,16 +92,29 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" } - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } delay(1000) assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) .isEqualTo(View.GONE) } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.getScenario().onActivity { activity -> action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } } From 1f97b8d3e460c356726fd2e43d1c5ab9378df405 Mon Sep 17 00:00:00 2001 From: maimoonak <4829880+maimoonak@users.noreply.github.com> Date: Mon, 30 May 2022 19:12:05 -0400 Subject: [PATCH 35/75] Update datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt Co-authored-by: aditya-07 --- .../datacapture/QuestionnaireViewModel.kt | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index e2a082d5be..6b8c7a46fc 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -207,42 +207,31 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat internal val questionnaireItemValueStateFlow = _questionnaireItemValueStateFlow.asSharedFlow() private fun runCalculatedExpressions() { - val calculableItems = - linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } - calculableItems.forEach { questionnaireItem -> - linkIdToQuestionnaireResponseItemMap[questionnaireItem.key]?.let { questionnaireResponseItem - -> - val expression = questionnaireItem.value.calculatedExpression!!.expression - fhirPathEngine.evaluate(null, questionnaireResponse, null, null, expression).run { - with(this.firstOrNull()) { - // update only if answer has changed, otherwise app can stuck in loop to keep updating - // changed items - val evaluatedAnswer = this?.castToType(this) - val currentAnswer = - if (questionnaireResponseItem.hasAnswer()) - questionnaireResponseItem.answerFirstRep.value - else null - - if (!(evaluatedAnswer == null && currentAnswer == null) && - evaluatedAnswer?.equalsDeep(currentAnswer) != true - ) { - questionnaireResponseItem.answerFirstRep.value = - evaluatedAnswer?.let { it.castToType(it) } - + linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } + .forEach { questionnaireItem -> + linkIdToQuestionnaireResponseItemMap[questionnaireItem.key]?.let { questionnaireResponseItem + -> + questionnaireItem.value.calculatedExpression?.expression?.let { expression -> + fhirPathEngine.evaluate(null, questionnaireResponse, null, null, expression).firstOrNull()?.let { + val evaluatedAnswer = it.castToType(it) + val currentAnswer = if (questionnaireResponseItem.hasAnswer()) questionnaireResponseItem.answerFirstRep.value else null + if (!evaluatedAnswer.equalsDeep(currentAnswer)) { + questionnaireResponseItem.answerFirstRep.value = evaluatedAnswer + } // notify UI to update it value i.e. notify item changed to adapter viewModelScope.launch { - if (modificationCount.value > 0) + if (modificationCount.value > 0) { questionnaireStateFlow.collectLatest { it.items .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } } + } } } } } } - } } fun detectCalculatedExpressionCyclicDependency() { From 9e3181904f232baf0559465a367aed06d455019e Mon Sep 17 00:00:00 2001 From: maimoonak Date: Tue, 31 May 2022 18:02:51 +0500 Subject: [PATCH 36/75] Move widget to LayoutList | Run Calculation after state-change --- .../calculated_expression_questionnaire.json | 5 +- .../fhir/catalog/ComponentListViewModel.kt | 5 -- .../fhir/catalog/LayoutListViewModel.kt | 5 ++ .../datacapture/QuestionnaireViewModel.kt | 63 ++++++++++++------- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/catalog/src/main/assets/calculated_expression_questionnaire.json b/catalog/src/main/assets/calculated_expression_questionnaire.json index 3e72d87668..c09021b0dc 100644 --- a/catalog/src/main/assets/calculated_expression_questionnaire.json +++ b/catalog/src/main/assets/calculated_expression_questionnaire.json @@ -5,6 +5,7 @@ "linkId": "a-birthdate", "text": "Birth Date", "type": "date", + "readOnly": true, "extension": [ { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression", @@ -22,9 +23,9 @@ "type": "quantity", "initial": [{ "valueQuantity": { - "unit": "months", + "unit": "years", "system": "http://unitsofmeasure.org", - "code": "months" + "code": "years" } }] }, diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index ab0abfdacd..c1f9f72bae 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -78,10 +78,5 @@ class ComponentListViewModel(application: Application, private val state: SavedS ), SLIDER(R.drawable.ic_slider, R.string.component_name_slider, "slider_questionnaire.json"), IMAGE(R.drawable.ic_image, R.string.component_name_image, ""), - CALCULATED_EXPRESSION( - R.drawable.ic_unitoptions, - R.string.component_name_calculated_expression, - "calculated_expression_questionnaire.json" - ), } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt index b734ecf614..2ffd979a67 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt @@ -48,5 +48,10 @@ class LayoutListViewModel(application: Application, private val state: SavedStat ), REVIEW(R.drawable.ic_reviewlayout, R.string.layout_name_review, ""), READ_ONLY(R.drawable.ic_readonlylayout, R.string.layout_name_read_only, ""), + CALCULATED_EXPRESSION( + R.drawable.ic_unitoptions, + R.string.component_name_calculated_expression, + "calculated_expression_questionnaire.json" + ), } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 6b8c7a46fc..c020858456 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -150,9 +150,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } } - - runCalculatedExpressions() - modificationCount.value += 1 } @@ -188,6 +185,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat questionnaireResponseItemList = questionnaireResponse.item, pagination = pagination, ) + .also { runCalculatedExpressions() } } .stateIn( viewModelScope, @@ -207,34 +205,50 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat internal val questionnaireItemValueStateFlow = _questionnaireItemValueStateFlow.asSharedFlow() private fun runCalculatedExpressions() { - linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } - .forEach { questionnaireItem -> - linkIdToQuestionnaireResponseItemMap[questionnaireItem.key]?.let { questionnaireResponseItem - -> - questionnaireItem.value.calculatedExpression?.expression?.let { expression -> - fhirPathEngine.evaluate(null, questionnaireResponse, null, null, expression).firstOrNull()?.let { - val evaluatedAnswer = it.castToType(it) - val currentAnswer = if (questionnaireResponseItem.hasAnswer()) questionnaireResponseItem.answerFirstRep.value else null - if (!evaluatedAnswer.equalsDeep(currentAnswer)) { + linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null }.forEach { + questionnaireItem -> + linkIdToQuestionnaireResponseItemMap[questionnaireItem.key]?.let { questionnaireResponseItem + -> + questionnaireItem.value.calculatedExpression?.expression?.let { expression -> + fhirPathEngine + .evaluate(null, questionnaireResponse, null, null, expression) + .firstOrNull() + .let { + val evaluatedAnswer = it?.castToType(it) + val currentAnswer = + if (questionnaireResponseItem.hasAnswer()) + questionnaireResponseItem.answerFirstRep.value + else null + + // update and notify only if answer has changed to prevent any loop or needless + // iterations + // the answer must be changed if both answers are different i.e. any of them is null + // or has a different value + if (!(evaluatedAnswer == null && currentAnswer == null) && + evaluatedAnswer?.equalsDeep(currentAnswer) != true + ) { questionnaireResponseItem.answerFirstRep.value = evaluatedAnswer - } - // notify UI to update it value i.e. notify item changed to adapter - viewModelScope.launch { - if (modificationCount.value > 0) { - questionnaireStateFlow.collectLatest { - it.items - .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } - .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } + 1 + // notify UI to update it value i.e. notify item changed to adapter + viewModelScope.launch { + if (modificationCount.value > 0) { + questionnaireStateFlow.collectLatest { + it.items + .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } + .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } + } } } } } - } } } + } } - fun detectCalculatedExpressionCyclicDependency() { + fun detectCalculatedExpressionCyclicDependency( + linkIdToQuestionnaireItemMap: Map + ) { val calculableItems = linkIdToQuestionnaireItemMap.filter { it.value.calculatedExpression != null } calculableItems.forEach { current -> @@ -305,6 +319,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat ) } } + return linkIdToQuestionnaireResponseItemMap } @@ -316,6 +331,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat for (item in questionnaireItemList) { linkIdToQuestionnaireItemMap.putAll(createLinkIdToQuestionnaireItemMap(item.item)) } + + detectCalculatedExpressionCyclicDependency(linkIdToQuestionnaireItemMap) + return linkIdToQuestionnaireItemMap } @@ -435,7 +453,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * when first opening this questionnaire. Otherwise, returns `null`. */ private fun Questionnaire.getInitialPagination(): QuestionnairePagination? { - detectCalculatedExpressionCyclicDependency() runCalculatedExpressions() val usesPagination = From 501f4ef680ff4a1e85f890809d8ae06b94638d3f Mon Sep 17 00:00:00 2001 From: maimoonak Date: Tue, 31 May 2022 20:17:24 +0500 Subject: [PATCH 37/75] Revert the run-expression after state-flow --- .../android/fhir/datacapture/QuestionnaireViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index c020858456..1587ce62d1 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -151,6 +151,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } modificationCount.value += 1 + + runCalculatedExpressions() } private val pageFlow = MutableStateFlow(questionnaire.getInitialPagination()) @@ -185,7 +187,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat questionnaireResponseItemList = questionnaireResponse.item, pagination = pagination, ) - .also { runCalculatedExpressions() } } .stateIn( viewModelScope, @@ -196,6 +197,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat questionnaireResponseItemList = questionnaireResponse.item, pagination = questionnaire.getInitialPagination(), ) + .also { runCalculatedExpressions() } ) /** @@ -228,7 +230,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat evaluatedAnswer?.equalsDeep(currentAnswer) != true ) { questionnaireResponseItem.answerFirstRep.value = evaluatedAnswer - 1 + // notify UI to update it value i.e. notify item changed to adapter viewModelScope.launch { if (modificationCount.value > 0) { @@ -453,8 +455,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat * when first opening this questionnaire. Otherwise, returns `null`. */ private fun Questionnaire.getInitialPagination(): QuestionnairePagination? { - runCalculatedExpressions() - val usesPagination = item.any { item -> item.extension.any { extension -> From b580a85af17b3f83307be41f4fff3e2c3a6d05e6 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 1 Jun 2022 17:45:56 +0700 Subject: [PATCH 38/75] Fix testing typo --- ...QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 9fc414b331..f5dc0c7fd4 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -51,7 +51,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { @Before fun setup() { activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } - viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) + viewHolder = QuestionnaireItemDisplayViewHolderFactory.create(parent) setTestLayout(viewHolder.itemView) } From f9c036ddeece92f8b1c67e2f8744178112e03d8b Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Thu, 2 Jun 2022 19:07:32 +0700 Subject: [PATCH 39/75] Trigger CI checks From 3107d7eed404bb862f7a5b73c6492ee9cf70b923 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 3 Jun 2022 14:55:09 +0700 Subject: [PATCH 40/75] Trigger CI checks From aee9dd29ee7b7537fb7d513bc6ddc0f2bb1ed3da Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Thu, 9 Jun 2022 11:16:28 +0700 Subject: [PATCH 41/75] Add test --- .../datacapture/utilities/MoreContextTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt new file mode 100644 index 0000000000..c34bbef690 --- /dev/null +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.utilities + +import android.app.Application +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class MoreContextTest { + + @Test + fun context_should_return_appCompatActivity() { + val context = AppCompatActivity().tryUnwrapContext() + assertThat(context is AppCompatActivity).isTrue() + } + + @Test + fun context_should_return_baseContext_as_appCompatActivity() { + val context = ContextThemeWrapper(AppCompatActivity(), 0).tryUnwrapContext() + assertThat(context is AppCompatActivity).isTrue() + } + + @Test + fun context_should_return_null() { + val context = Application().tryUnwrapContext() + assertThat(context == null).isTrue() + } +} From 42877275a206510d5263fdda87f3155927bd0a8c Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 10 Jun 2022 08:03:01 +0700 Subject: [PATCH 42/75] Update test --- .../android/fhir/datacapture/utilities/MoreContextTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt index c34bbef690..fc5d5480e3 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt @@ -30,18 +30,18 @@ class MoreContextTest { @Test fun context_should_return_appCompatActivity() { val context = AppCompatActivity().tryUnwrapContext() - assertThat(context is AppCompatActivity).isTrue() + assertThat(context).isInstanceOf(AppCompatActivity::class.java) } @Test fun context_should_return_baseContext_as_appCompatActivity() { val context = ContextThemeWrapper(AppCompatActivity(), 0).tryUnwrapContext() - assertThat(context is AppCompatActivity).isTrue() + assertThat(context).isInstanceOf(AppCompatActivity::class.java) } @Test fun context_should_return_null() { val context = Application().tryUnwrapContext() - assertThat(context == null).isTrue() + assertThat(context).isNull() } } From b2eab51857673e283f61ac28e8f354c40c5a3fa0 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 10 Jun 2022 08:03:07 +0700 Subject: [PATCH 43/75] Add test --- ...mGroupViewHolderFactoryInstrumentedTest.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 31903d339f..c22bcf0af2 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -86,6 +86,37 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { .isEqualTo(View.VISIBLE) } + @Test + fun shouldHideImageView_whenItemImageExtensionIsSet_withNonImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "document/pdf" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = + listOf( + Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) + ) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } + @Test fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { val questionnaireItemComponent = From c48d220467a9fa5532a6263becf787915053df41 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 10 Jun 2022 13:54:18 +0700 Subject: [PATCH 44/75] Trigger CI checks From c8ded572f42ac71f40b3dca0505d06e21aa9cf2c Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 1 Jul 2022 19:04:06 +0700 Subject: [PATCH 45/75] Remove commented code --- .../android/fhir/demo/ReferenceAttachmentResolver.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt index 2be8e8160a..b8ba0706cf 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt @@ -32,15 +32,5 @@ class ReferenceAttachmentResolver(val context: Context) : AttachmentResolver { override suspend fun resolveImageUrl(uri: String): Bitmap? { return null - /*return HapiFhirService.create(FhirContext.forR4().newJsonParser()) - .fetchImage(uri) - .execute() - .run { - if (this.body() != null) { - BitmapFactory.decodeStream(this.body()?.byteStream()) - } else { - null - } - }*/ } } From bc45196bdf617404e90f7e82a5f57d51ffe95b6a Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Fri, 1 Jul 2022 19:07:36 +0700 Subject: [PATCH 46/75] Revert orientation to horizontal --- .../main/res/layout/questionnaire_item_group_header_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml index 0d5550dc79..1a1cfd99fb 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_group_header_view.xml @@ -18,7 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" + android:orientation="horizontal" > Date: Tue, 5 Jul 2022 00:55:36 +0500 Subject: [PATCH 47/75] remove empty line changes --- .../fhir/catalog/ComponentListViewModel.kt | 5 + .../fhir/catalog/LayoutListViewModel.kt | 5 - ...xtQuantityViewHolderFactoryEspressoTest.kt | 134 ++++++++++++++++++ .../datacapture/QuestionnaireViewModel.kt | 11 +- ...reItemEditTextQuantityViewHolderFactory.kt | 4 +- .../QuestionnaireItemViewHolderFactory.kt | 2 +- .../MoreQuestionnaireItemComponentsTest.kt | 31 ++++ ...emEditTextQuantityViewHolderFactoryTest.kt | 41 ------ 8 files changed, 176 insertions(+), 57 deletions(-) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index 7dbd958037..06fe2bf91f 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -83,5 +83,10 @@ class ComponentListViewModel(application: Application, private val state: SavedS R.string.component_name_auto_complete, "auto_complete_questionnaire.json" ), + CALCULATED_EXPRESSION( + R.drawable.ic_unitoptions, + R.string.component_name_calculated_expression, + "calculated_expression_questionnaire.json" + ), } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt index 2ffd979a67..b734ecf614 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt @@ -48,10 +48,5 @@ class LayoutListViewModel(application: Application, private val state: SavedStat ), REVIEW(R.drawable.ic_reviewlayout, R.string.layout_name_review, ""), READ_ONLY(R.drawable.ic_readonlylayout, R.string.layout_name_read_only, ""), - CALCULATED_EXPRESSION( - R.drawable.ic_unitoptions, - R.string.component_name_calculated_expression, - "calculated_expression_questionnaire.json" - ), } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt new file mode 100644 index 0000000000..3900941cee --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity +import com.google.android.fhir.datacapture.utilities.showDropDown +import com.google.common.truth.Truth.assertThat +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.Quantity +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.math.BigDecimal + +class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setup() { + activityScenarioRule.scenario.onActivity { activity -> parent = FrameLayout(activity) } + viewHolder = QuestionnaireItemEditTextQuantityViewHolderFactory.create(parent) + setTestLayout(viewHolder.itemView) + } + + @Test + fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { + val questionnaireItemViewItem = QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + required = true + addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity().apply { + code = "months" + system = "http://unitofmeasure.com" + } + ) + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + runOnUI { viewHolder.bind(questionnaireItemViewItem) } + + onView(withId(R.id.text_input_edit_text)).perform(click()) + onView(withId(R.id.text_input_edit_text)).perform(typeText("22")) + assertThat(viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString()) + .isEqualTo("22") + + val delegateValue = + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity + assertThat(delegateValue.code).isEqualTo("months") + assertThat(delegateValue.system).isEqualTo("http://unitofmeasure.com") + assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) + + val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + assertThat(responseValue.code).isEqualTo("months") + assertThat(responseValue.system).isEqualTo("http://unitofmeasure.com") + assertThat(responseValue.value).isEqualTo(BigDecimal(22)) + } + + @Test + fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { required = true }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + runOnUI { viewHolder.bind(questionnaireItemViewItem) } + + onView(withId(R.id.text_input_edit_text)).perform(click()) + onView(withId(R.id.text_input_edit_text)).perform(typeText("22")) + assertThat(viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString()) + .isEqualTo("22") + + val delegateValue = + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity + assertThat(delegateValue.code).isNull() + assertThat(delegateValue.system).isNull() + assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) + + val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + assertThat(delegateValue.code).isNull() + assertThat(delegateValue.system).isNull() + assertThat(responseValue.value).isEqualTo(BigDecimal(22)) + } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.scenario.onActivity { action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.scenario.onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 1587ce62d1..c9aa25cc9e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -34,9 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext import org.hl7.fhir.r4.model.CodeableConcept @@ -261,10 +259,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } // if any calculable expression depends on this item and this item is referring to the // dependent item in its own expression then raise error - if (otherDependent != null && currentExpression.contains("'${otherDependent.linkId}'")) - throw IllegalStateException( - "${current.key} and ${otherDependent.linkId} have cyclic dependency in calculated-expression extension" - ) + check(otherDependent != null && currentExpression.contains("'${otherDependent.linkId}'")) { + "${current.key} and ${otherDependent!!.linkId} have cyclic dependency in calculated-expression extension" + } } } @@ -321,7 +318,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat ) } } - return linkIdToQuestionnaireResponseItemMap } @@ -416,7 +412,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat .items } .toList() - return QuestionnaireState(items = items, pagination = pagination) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index 90186f7b7c..29f5ce6429 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -38,7 +38,7 @@ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : ): QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent? { // https://build.fhir.org/ig/HL7/sdc/behavior.html#initial // read default unit from initial, as ideally quantity must specify a unit - return text.toDoubleOrNull()?.let { + return text.let { val quantity = with(questionnaireItemViewItem.questionnaireItem) { if (this.hasInitial() && this.initialFirstRep.valueQuantity.hasCode()) @@ -49,7 +49,7 @@ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : this.system = initial.system } } - else Quantity(it) + else Quantity().apply { this.value = BigDecimal(text) } } QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(quantity) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt index 48a990626a..f1d117c5be 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt @@ -52,7 +52,7 @@ abstract class QuestionnaireItemViewHolderFactory(@LayoutRes val resId: Int) { */ open class QuestionnaireItemViewHolder( itemView: View, - internal val delegate: QuestionnaireItemViewHolderDelegate + private val delegate: QuestionnaireItemViewHolderDelegate ) : RecyclerView.ViewHolder(itemView) { init { delegate.init(itemView) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index f988c2af88..9e988fca00 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -614,6 +614,37 @@ class MoreQuestionnaireItemComponentsTest { assertThat(questionItem.itemFirstRep.enableWhenExpression).isNull() } + @Test + fun calculatedExpression_shouldReturnExpression() { + val questionnaire = + Questionnaire.QuestionnaireItemComponent().apply { + addExtension( + EXTENSION_CALCULATED_EXPRESSION_URL, + Expression().apply { + this.expression = "today()" + this.language = "text/fhirpath" + } + ) + } + assertThat(questionnaire.calculatedExpression).isNotNull() + assertThat(questionnaire.calculatedExpression!!.expression).isEqualTo("today()") + } + + @Test + fun calculatedExpression_shouldReturnNull() { + val questionnaire = + Questionnaire.QuestionnaireItemComponent().apply { + addExtension( + ITEM_INITIAL_EXPRESSION_URL, + Expression().apply { + this.expression = "today()" + this.language = "text/fhirpath" + } + ) + } + assertThat(questionnaire.calculatedExpression).isNull() + } + @Test fun localizedFlyoverSpanned_matchingLocale_shouldReturnFlyover() { val questionItemList = diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt index 7b94616762..1c29f3ddb1 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt @@ -212,45 +212,4 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryTest { ) .isFalse() } - - @Test - fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.QuestionnaireItemComponent().apply { - required = true - addInitial( - Questionnaire.QuestionnaireItemInitialComponent( - Quantity().apply { - code = "months" - system = "http://unitofmeasure.com" - } - ) - ) - }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - val value = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity - assertThat(value.code).isEqualTo("months") - assertThat(value.system).isEqualTo("http://unitofmeasure.com") - assertThat(value.value).isEqualTo(BigDecimal(22)) - } - - fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.QuestionnaireItemComponent().apply { required = true }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} - ) - val value = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity - assertThat(value.code).isNull() - assertThat(value.system).isNull() - assertThat(value.value).isEqualTo(BigDecimal(22)) - } } From bbda724553c9cb242853e6963b155098caeaa7ce Mon Sep 17 00:00:00 2001 From: maimoonak Date: Tue, 5 Jul 2022 01:01:12 +0500 Subject: [PATCH 48/75] spotless fix --- .../fhir/catalog/ComponentListViewModel.kt | 8 +- ...xtQuantityViewHolderFactoryEspressoTest.kt | 73 ++++++++++--------- .../MoreQuestionnaireItemComponents.kt | 2 +- .../fhir/datacapture/QuestionnaireFragment.kt | 2 +- .../datacapture/mapping/ResourceMapper.kt | 2 +- ...ionnaireItemDatePickerViewHolderFactory.kt | 2 +- ...reItemEditTextQuantityViewHolderFactory.kt | 2 +- .../MoreQuestionnaireItemComponentsTest.kt | 62 ++++++++-------- .../datacapture/QuestionnaireFragmentTest.kt | 2 +- ...aireItemDatePickerViewHolderFactoryTest.kt | 2 +- 10 files changed, 79 insertions(+), 78 deletions(-) diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index 06fe2bf91f..04972903eb 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,9 +84,9 @@ class ComponentListViewModel(application: Application, private val state: SavedS "auto_complete_questionnaire.json" ), CALCULATED_EXPRESSION( - R.drawable.ic_unitoptions, - R.string.component_name_calculated_expression, - "calculated_expression_questionnaire.json" + R.drawable.ic_unitoptions, + R.string.component_name_calculated_expression, + "calculated_expression_questionnaire.json" ), } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt index 3900941cee..44cd10dfca 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,25 +22,19 @@ import android.widget.TextView import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity -import com.google.android.fhir.datacapture.utilities.showDropDown import com.google.common.truth.Truth.assertThat -import org.hl7.fhir.r4.model.Coding +import java.math.BigDecimal import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before import org.junit.Rule import org.junit.Test -import java.math.BigDecimal class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { @Rule @@ -60,35 +54,39 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { @Test fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.QuestionnaireItemComponent().apply { - required = true - addInitial( - Questionnaire.QuestionnaireItemInitialComponent( - Quantity().apply { - code = "months" - system = "http://unitofmeasure.com" - } - ) - ) - }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + required = true + addInitial( + Questionnaire.QuestionnaireItemInitialComponent( + Quantity().apply { + code = "months" + system = "http://unitofmeasure.com" + } + ) + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} runOnUI { viewHolder.bind(questionnaireItemViewItem) } onView(withId(R.id.text_input_edit_text)).perform(click()) onView(withId(R.id.text_input_edit_text)).perform(typeText("22")) - assertThat(viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString()) - .isEqualTo("22") + assertThat( + viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() + ) + .isEqualTo("22") val delegateValue = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity assertThat(delegateValue.code).isEqualTo("months") assertThat(delegateValue.system).isEqualTo("http://unitofmeasure.com") assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) - val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + val responseValue = + questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity assertThat(responseValue.code).isEqualTo("months") assertThat(responseValue.system).isEqualTo("http://unitofmeasure.com") assertThat(responseValue.value).isEqualTo(BigDecimal(22)) @@ -97,25 +95,28 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { @Test fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { val questionnaireItemViewItem = - QuestionnaireItemViewItem( - Questionnaire.QuestionnaireItemComponent().apply { required = true }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { required = true }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} runOnUI { viewHolder.bind(questionnaireItemViewItem) } onView(withId(R.id.text_input_edit_text)).perform(click()) onView(withId(R.id.text_input_edit_text)).perform(typeText("22")) - assertThat(viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString()) - .isEqualTo("22") + assertThat( + viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() + ) + .isEqualTo("22") val delegateValue = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity + (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! + .valueQuantity assertThat(delegateValue.code).isNull() assertThat(delegateValue.system).isNull() assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) - val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + val responseValue = + questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity assertThat(delegateValue.code).isNull() assertThat(delegateValue.system).isNull() assertThat(responseValue.value).isEqualTo(BigDecimal(22)) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 5cd33dd4dd..0c4a7c60ef 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt index 75c22915aa..3bc4936f01 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt index 79ef73515d..9d37b5bdd8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 4aa9ee3a88..c2cda1a886 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index 29f5ce6429..bb9968ce05 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 9e988fca00..c5b1e20e7c 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -614,36 +614,36 @@ class MoreQuestionnaireItemComponentsTest { assertThat(questionItem.itemFirstRep.enableWhenExpression).isNull() } - @Test - fun calculatedExpression_shouldReturnExpression() { - val questionnaire = - Questionnaire.QuestionnaireItemComponent().apply { - addExtension( - EXTENSION_CALCULATED_EXPRESSION_URL, - Expression().apply { - this.expression = "today()" - this.language = "text/fhirpath" - } - ) - } - assertThat(questionnaire.calculatedExpression).isNotNull() - assertThat(questionnaire.calculatedExpression!!.expression).isEqualTo("today()") - } - - @Test - fun calculatedExpression_shouldReturnNull() { - val questionnaire = - Questionnaire.QuestionnaireItemComponent().apply { - addExtension( - ITEM_INITIAL_EXPRESSION_URL, - Expression().apply { - this.expression = "today()" - this.language = "text/fhirpath" - } - ) - } - assertThat(questionnaire.calculatedExpression).isNull() - } + @Test + fun calculatedExpression_shouldReturnExpression() { + val questionnaire = + Questionnaire.QuestionnaireItemComponent().apply { + addExtension( + EXTENSION_CALCULATED_EXPRESSION_URL, + Expression().apply { + this.expression = "today()" + this.language = "text/fhirpath" + } + ) + } + assertThat(questionnaire.calculatedExpression).isNotNull() + assertThat(questionnaire.calculatedExpression!!.expression).isEqualTo("today()") + } + + @Test + fun calculatedExpression_shouldReturnNull() { + val questionnaire = + Questionnaire.QuestionnaireItemComponent().apply { + addExtension( + ITEM_INITIAL_EXPRESSION_URL, + Expression().apply { + this.expression = "today()" + this.language = "text/fhirpath" + } + ) + } + assertThat(questionnaire.calculatedExpression).isNull() + } @Test fun localizedFlyoverSpanned_matchingLocale_shouldReturnFlyover() { diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt index f7ea076010..5e34825194 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireFragmentTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt index ec8d81c0ac..5d1e84600b 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 1fd726cde059bdc1223c2c5589913db0057cd320 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Tue, 5 Jul 2022 04:29:11 +0500 Subject: [PATCH 49/75] Esperesso test | Fix failing test --- ...extQuantityViewHolderFactoryEspressoTest.kt | 18 ++---------------- .../fhir/datacapture/QuestionnaireViewModel.kt | 16 +++++++--------- ...ireItemEditTextQuantityViewHolderFactory.kt | 7 ++++--- .../QuestionnaireItemViewHolderFactory.kt | 4 +++- ...temEditTextQuantityViewHolderFactoryTest.kt | 4 ++-- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt index 44cd10dfca..ed06863a77 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -78,13 +78,6 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) .isEqualTo("22") - val delegateValue = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity - assertThat(delegateValue.code).isEqualTo("months") - assertThat(delegateValue.system).isEqualTo("http://unitofmeasure.com") - assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) - val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity assertThat(responseValue.code).isEqualTo("months") @@ -108,17 +101,10 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) .isEqualTo("22") - val delegateValue = - (viewHolder.delegate as QuestionnaireItemEditTextViewHolderDelegate).getValue("22")!! - .valueQuantity - assertThat(delegateValue.code).isNull() - assertThat(delegateValue.system).isNull() - assertThat(delegateValue.value).isEqualTo(BigDecimal(22)) - val responseValue = questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity - assertThat(delegateValue.code).isNull() - assertThat(delegateValue.system).isNull() + assertThat(responseValue.code).isNull() + assertThat(responseValue.system).isNull() assertThat(responseValue.value).isEqualTo(BigDecimal(22)) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index da1b9ff6c1..e337f21946 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -150,9 +150,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } } - modificationCount.value += 1 - runCalculatedExpressions() + + modificationCount.value += 1 } private val pageFlow = MutableStateFlow(questionnaire.getInitialPagination()) @@ -233,12 +233,10 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat // notify UI to update it value i.e. notify item changed to adapter viewModelScope.launch { - if (modificationCount.value > 0) { - questionnaireStateFlow.collectLatest { - it.items - .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } - .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } - } + questionnaireStateFlow.collectLatest { + it.items + .indexOfFirst { it.questionnaireItem.linkId == questionnaireItem.key } + .let { if (it > -1) _questionnaireItemValueStateFlow.emit(it) } } } } @@ -261,7 +259,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } // if any calculable expression depends on this item and this item is referring to the // dependent item in its own expression then raise error - check(otherDependent != null && currentExpression.contains("'${otherDependent.linkId}'")) { + check(otherDependent == null || !currentExpression.contains("'${otherDependent.linkId}'")) { "${current.key} and ${otherDependent!!.linkId} have cyclic dependency in calculated-expression extension" } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index bb9968ce05..c209af5194 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -38,18 +38,19 @@ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : ): QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent? { // https://build.fhir.org/ig/HL7/sdc/behavior.html#initial // read default unit from initial, as ideally quantity must specify a unit - return text.let { + return text.takeIf { it.isNotBlank() }?.let { + val value = BigDecimal(text) val quantity = with(questionnaireItemViewItem.questionnaireItem) { if (this.hasInitial() && this.initialFirstRep.valueQuantity.hasCode()) this.initialFirstRep.valueQuantity.let { initial -> Quantity().apply { - this.value = BigDecimal(text) + this.value = value this.code = initial.code this.system = initial.system } } - else Quantity().apply { this.value = BigDecimal(text) } + else Quantity().apply { this.value = value } } QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().setValue(quantity) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt index f1d117c5be..c244277d64 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import androidx.annotation.LayoutRes import androidx.recyclerview.widget.RecyclerView import com.google.android.fhir.datacapture.validation.QuestionnaireResponseItemValidator import com.google.android.fhir.datacapture.validation.ValidationResult +import com.google.common.annotations.VisibleForTesting /** * Factory for [QuestionnaireItemViewHolder]. @@ -52,6 +53,7 @@ abstract class QuestionnaireItemViewHolderFactory(@LayoutRes val resId: Int) { */ open class QuestionnaireItemViewHolder( itemView: View, + @org.jetbrains.annotations.VisibleForTesting private val delegate: QuestionnaireItemViewHolderDelegate ) : RecyclerView.ViewHolder(itemView) { init { diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt index 1c29f3ddb1..8ceb952a7b 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,7 +120,7 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryTest { val answer = questionnaireItemViewItem.questionnaireResponseItem.answer assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].valueQuantity!!.value!!.toString()).isEqualTo("10.0") + assertThat(answer[0].valueQuantity!!.value!!.toString()).isEqualTo("10") } @Test From 6748dfe70c4aaa09373b78a6f7cc7070c47ba2e2 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 6 Jul 2022 14:20:40 +0700 Subject: [PATCH 50/75] Use IO Dispatcher to fetch Bitmap --- .../views/QuestionnaireItemDisplayViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 6e154d5f7b..7ef75f9e2a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -47,7 +47,7 @@ internal object QuestionnaireItemDisplayViewHolderFactory : questionnaireItemViewItem.questionnaireItem.itemImage?.let { val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch { + activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index eb58c86142..be9ba63f71 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -100,7 +100,7 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate( questionnaireItemViewItem.questionnaireItem.itemImage?.let { val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch { + activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index c00b972492..904535cb14 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -51,7 +51,7 @@ internal object QuestionnaireItemGroupViewHolderFactory : questionnaireItemViewItem.questionnaireItem.itemImage?.let { val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch { + activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE From 15d080844e6a5ab3f5a2cc735977d48a5da15c32 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 6 Jul 2022 14:20:54 +0700 Subject: [PATCH 51/75] Remove unused comment --- .../views/QuestionnaireItemDisplayViewHolderFactory.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 7ef75f9e2a..5f349be7fb 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -52,8 +52,6 @@ internal object QuestionnaireItemDisplayViewHolderFactory : activity.lifecycleScope.launch(Dispatchers.Main) { itemImageView.visibility = View.VISIBLE itemImageView.setImageBitmap(this@run) - - // throw RuntimeException("At last! This has run") } } } From f1d6ec3feeaa350cb111947ac5e6b1f6a0eb5332 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 6 Jul 2022 14:25:38 +0700 Subject: [PATCH 52/75] spotlessApply --- .../barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt | 2 +- .../android/fhir/datacapture/DataCaptureTestApplication.kt | 2 +- .../MoreQuestionnaireItemComponentsInstrumentedTest.kt | 2 +- ...QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 2 +- ...reItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt | 2 +- .../QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt | 2 +- .../com/google/android/fhir/datacapture/DataCaptureConfig.kt | 2 +- .../com/google/android/fhir/datacapture/MoreAnswerOptions.kt | 2 +- .../android/fhir/datacapture/MoreQuestionnaireItemComponents.kt | 2 +- .../google/android/fhir/datacapture/QuestionnaireItemAdapter.kt | 2 +- .../google/android/fhir/datacapture/utilities/MoreContext.kt | 2 +- .../views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDatePickerViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDateTimePickerViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDialogSelectViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDisplayViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDropDownViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 2 +- .../google/android/fhir/datacapture/MoreAnswerOptionsTest.kt | 2 +- .../fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt | 2 +- .../android/fhir/datacapture/QuestionnaireItemAdapterTest.kt | 2 +- .../fhir/datacapture/testing/DataCaptureTestApplication.kt | 2 +- .../android/fhir/datacapture/utilities/MoreContextTest.kt | 2 +- .../views/QuestionnaireItemDropDownViewHolderFactoryTest.kt | 2 +- .../main/java/com/google/android/fhir/demo/FhirApplication.kt | 2 +- .../com/google/android/fhir/demo/ReferenceAttachmentResolver.kt | 2 +- .../google/android/fhir/demo/data/DownloadWorkManagerImpl.kt | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt index 4c0b5d951c..2c99adef36 100644 --- a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt +++ b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt index 7ebe3d91fc..d45ec16ed3 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/DataCaptureTestApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt index 4af6d394e6..b770e97863 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsInstrumentedTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index f5dc0c7fd4..f53f1eb469 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 7990939369..7d09c97d65 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index c22bcf0af2..1a6f65c9c5 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt index 08a1c8d523..eff90fac71 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/DataCaptureConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt index 2cc880b997..6e32d434fc 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index e91915ac4c..7cbd17bc33 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt index 462377d665..f1465ab0e4 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt index 0c4af923a2..98cc2e8479 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/utilities/MoreContext.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt index 71241fec32..b088fc0b07 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index a745af8f86..3004825bfc 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt index f51702ec91..e262084123 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt index dbab7b37b0..4bd9d6f07f 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 5f349be7fb..a4fbecf0ab 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt index 0ca6ac9772..7152495253 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index be9ba63f71..dc5752ca31 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 904535cb14..98b670fd7f 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt index 4ded26f91e..4ed5bff259 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt index 43a60f2269..01641114f0 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponentsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt index 7b718d6561..4cffc0cb35 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt index 83e40cdf00..6389d84335 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/testing/DataCaptureTestApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt index fc5d5480e3..2d749b4dd1 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/utilities/MoreContextTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryTest.kt index f34b3098bd..3bb65fb52e 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt b/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt index 8344863780..7f3ff01995 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/FhirApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt index b8ba0706cf..5f33f586e4 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/ReferenceAttachmentResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt b/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt index 5fafcdd334..c9165b5584 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From e738864c46f589fcb835d7f5900d947fbbae0620 Mon Sep 17 00:00:00 2001 From: maimoonak <4829880+maimoonak@users.noreply.github.com> Date: Wed, 20 Jul 2022 10:45:49 -0400 Subject: [PATCH 53/75] Remove unnessary changes --- .../android/fhir/datacapture/QuestionnaireFragment.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt index a3a8bdfcde..3a48f8da09 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,12 +103,6 @@ open class QuestionnaireFragment : Fragment() { } } } - - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - /* TODO viewModel.questionnaireItemValueStateFlow.collect { index -> - if (!recyclerView.isComputingLayout) adapter.notifyItemChanged(index) - }*/ - } } /** From 4b4aaaf1355dd9786b4300a2bea3d0b1926c0c13 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Wed, 20 Jul 2022 20:25:05 +0500 Subject: [PATCH 54/75] Fix espresso tests --- ...tTextQuantityViewHolderFactoryEspressoTest.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt index ed06863a77..be029a2c3c 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -67,8 +67,10 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) ) }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) runOnUI { viewHolder.bind(questionnaireItemViewItem) } onView(withId(R.id.text_input_edit_text)).perform(click()) @@ -79,7 +81,7 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { .isEqualTo("22") val responseValue = - questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + questionnaireItemViewItem.answers.first().valueQuantity assertThat(responseValue.code).isEqualTo("months") assertThat(responseValue.system).isEqualTo("http://unitofmeasure.com") assertThat(responseValue.value).isEqualTo(BigDecimal(22)) @@ -90,8 +92,10 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { val questionnaireItemViewItem = QuestionnaireItemViewItem( Questionnaire.QuestionnaireItemComponent().apply { required = true }, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) runOnUI { viewHolder.bind(questionnaireItemViewItem) } onView(withId(R.id.text_input_edit_text)).perform(click()) @@ -102,7 +106,7 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { .isEqualTo("22") val responseValue = - questionnaireItemViewItem.questionnaireResponseItem.answer.first().valueQuantity + questionnaireItemViewItem.answers.first().valueQuantity assertThat(responseValue.code).isNull() assertThat(responseValue.system).isNull() assertThat(responseValue.value).isEqualTo(BigDecimal(22)) From c6032aaf478436331891083b1c38e0be87088c23 Mon Sep 17 00:00:00 2001 From: maimoonak Date: Wed, 20 Jul 2022 21:38:09 +0500 Subject: [PATCH 55/75] Ignore Failing tests --- ...TextQuantityViewHolderFactoryEspressoTest.kt | 17 +++++++++-------- .../fhir/datacapture/QuestionnaireViewModel.kt | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt index be029a2c3c..1a5e485224 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -53,6 +54,7 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { } @Test + @Ignore("EditText does not call onChange https://github.com/google/android-fhir/issues/1498") fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { val questionnaireItemViewItem = QuestionnaireItemViewItem( @@ -68,8 +70,8 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) }, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, - answersChangedCallback = { _, _, _ -> }, + validationResult = null, + answersChangedCallback = { _, _, _ -> }, ) runOnUI { viewHolder.bind(questionnaireItemViewItem) } @@ -80,21 +82,21 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) .isEqualTo("22") - val responseValue = - questionnaireItemViewItem.answers.first().valueQuantity + val responseValue = questionnaireItemViewItem.answers.first().valueQuantity assertThat(responseValue.code).isEqualTo("months") assertThat(responseValue.system).isEqualTo("http://unitofmeasure.com") assertThat(responseValue.value).isEqualTo(BigDecimal(22)) } @Test + @Ignore("EditText does not call onChange https://github.com/google/android-fhir/issues/1498") fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { val questionnaireItemViewItem = QuestionnaireItemViewItem( Questionnaire.QuestionnaireItemComponent().apply { required = true }, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, - answersChangedCallback = { _, _, _ -> }, + validationResult = null, + answersChangedCallback = { _, _, _ -> }, ) runOnUI { viewHolder.bind(questionnaireItemViewItem) } @@ -105,8 +107,7 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { ) .isEqualTo("22") - val responseValue = - questionnaireItemViewItem.answers.first().valueQuantity + val responseValue = questionnaireItemViewItem.answers.first().valueQuantity assertThat(responseValue.code).isNull() assertThat(responseValue.system).isNull() assertThat(responseValue.value).isEqualTo(BigDecimal(22)) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 2e3e173778..4974bb6793 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -269,7 +269,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat Triple( item, modifiedQuestionnaireResponseItemSet.none { it.linkId == item.linkId }, - getQuestionnaireResponseItem(item.linkId) + questionnaireResponseItemPreOrderList.find { it.linkId == item.linkId } ) } .filter { calculable -> From 7fc8ba161eb5b57344a9585634365c22cafde955 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 27 Jul 2022 17:09:48 +0700 Subject: [PATCH 56/75] Use internal tryUnwrapContext for barcode --- common/build.gradle.kts | 1 + ...naireItemBarCodeReaderViewHolderFactory.kt | 2 +- .../contrib/views/barcode/mlkit/md/Utils.kt | 25 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1a018f81af..5da72882d1 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -30,6 +30,7 @@ configurations { all { exclude(module = "xpp3") } } dependencies { api(Dependencies.HapiFhir.structuresR4) + implementation(Dependencies.Androidx.appCompat) implementation(Dependencies.fhirUcum) testImplementation(Dependencies.Kotlin.kotlinTestJunit) diff --git a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt index 2c99adef36..ebe54d31ca 100644 --- a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt +++ b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/QuestionnaireItemBarCodeReaderViewHolderFactory.kt @@ -22,9 +22,9 @@ import android.view.View import android.widget.TextView import androidx.fragment.app.FragmentResultListener import com.google.android.fhir.datacapture.contrib.views.barcode.mlkit.md.LiveBarcodeScanningFragment +import com.google.android.fhir.datacapture.contrib.views.barcode.mlkit.md.Utils.tryUnwrapContext import com.google.android.fhir.datacapture.localizedPrefixSpanned import com.google.android.fhir.datacapture.localizedTextSpanned -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.views.QuestionnaireItemViewHolderDelegate import com.google.android.fhir.datacapture.views.QuestionnaireItemViewHolderFactory diff --git a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/Utils.kt b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/Utils.kt index 0c54370319..e4a524c3cc 100644 --- a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/Utils.kt +++ b/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/Utils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import android.graphics.RectF import android.graphics.YuvImage import android.hardware.Camera import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper import androidx.exifinterface.media.ExifInterface import com.google.android.fhir.datacapture.contrib.views.barcode.mlkit.md.camera.CameraSizePair import com.google.mlkit.vision.barcode.BarcodeScanning @@ -224,4 +226,25 @@ object Utils { } fun getBarcodeScanningClient() = BarcodeScanning.getClient() + + /** + * Returns the [AppCompatActivity] if there exists one wrapped inside [ContextThemeWrapper] s, or + * `null` otherwise. + * + * This function is inspired by the function with the same name in `AppCompateDelegateImpl`. See + * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=1615 + * + * TODO: find a more robust way to do this as it is not guaranteed that the activity is an + * AppCompatActivity. + */ + internal fun Context.tryUnwrapContext(): AppCompatActivity? { + var context = this + while (true) { + when (context) { + is AppCompatActivity -> return context + is ContextThemeWrapper -> context = context.baseContext + else -> return null + } + } + } } From 9302aa0c046c113178f25f8fd35b7bd8f4f66f2a Mon Sep 17 00:00:00 2001 From: maimoonak Date: Fri, 5 Aug 2022 16:23:58 +0500 Subject: [PATCH 57/75] Revert ignore test | merge main | refactor --- ...eItemEditTextQuantityViewHolderFactoryEspressoTest.kt | 3 --- .../fhir/datacapture/MoreQuestionnaireItemComponents.kt | 9 +++++++++ .../android/fhir/datacapture/QuestionnaireViewModel.kt | 5 ----- ...QuestionnaireItemEditTextQuantityViewHolderFactory.kt | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt index 1a5e485224..6dfda5dc2e 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest.kt @@ -33,7 +33,6 @@ import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -54,7 +53,6 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { } @Test - @Ignore("EditText does not call onChange https://github.com/google/android-fhir/issues/1498") fun getValue_WithInitial_shouldReturnQuantityWithUnitAndSystem() { val questionnaireItemViewItem = QuestionnaireItemViewItem( @@ -89,7 +87,6 @@ class QuestionnaireItemEditTextQuantityViewHolderFactoryEspressoTest { } @Test - @Ignore("EditText does not call onChange https://github.com/google/android-fhir/issues/1498") fun getValue_WithoutInitial_shouldReturnQuantityWithoutUnitAndSystem() { val questionnaireItemViewItem = QuestionnaireItemViewItem( diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index af66a42a9f..d1a751a307 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -301,6 +301,15 @@ fun QuestionnaireResponse.QuestionnaireResponseItemComponent.addNestedItemsToAns } } +/** + * Flatten a nested list of [QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent] + * recursively and returns a flat list of all items into list embedded at any level + */ +fun List.flattened(): + List { + return this + this.flatMap { if (it.hasItem()) it.item.flattened() else it.item } +} + /** * Creates a list of [QuestionnaireResponse.QuestionnaireResponseItemComponent]s from the nested * items in the [Questionnaire.QuestionnaireItemComponent]. diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index 4974bb6793..17b2495e9e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -312,11 +312,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat } } - fun List.flattened(): - List { - return this + this.flatMap { if (it.hasItem()) it.item.flattened() else it.item } - } - fun detectCalculatedExpressionCyclicDependency( items: List ) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index fb818e470a..cedc976d9c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -17,8 +17,8 @@ package com.google.android.fhir.datacapture.views import android.text.InputType -import java.math.BigDecimal import com.google.android.fhir.datacapture.R +import java.math.BigDecimal import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.QuestionnaireResponse From 6ba5378f1ab7a77d61953feacaa5432b6403fae0 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 07:31:03 +0700 Subject: [PATCH 58/75] Fix failing build checks --- ...DisplayViewHolderFactoryInstrumentedTest.kt | 12 ++++++++---- ...gleLineViewHolderFactoryInstrumentedTest.kt | 12 ++++++++---- ...emGroupViewHolderFactoryInstrumentedTest.kt | 18 ++++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index f53f1eb469..073f91ec8a 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -75,8 +75,10 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } @@ -96,8 +98,10 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 7d09c97d65..efe388f878 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -75,8 +75,10 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } @@ -96,8 +98,10 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 1a6f65c9c5..4010a51ddb 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -75,8 +75,10 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } @@ -106,8 +108,10 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } @@ -127,8 +131,10 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { viewHolder.bind( QuestionnaireItemViewItem( questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent() - ) {} + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = null, + answersChangedCallback = { _, _, _ -> }, + ) ) } From 5ec6ce686f60cadd976b836ee62c9ee014022ab5 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 07:45:37 +0700 Subject: [PATCH 59/75] Use NotValidated in test --- ...ionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 5 +++-- ...mEditTextSingleLineViewHolderFactoryInstrumentedTest.kt | 5 +++-- ...stionnaireItemGroupViewHolderFactoryInstrumentedTest.kt | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 073f91ec8a..87b0beb5ba 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity +import com.google.android.fhir.datacapture.validation.NotValidated import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -76,7 +77,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) @@ -99,7 +100,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index efe388f878..b9a89a1b20 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity +import com.google.android.fhir.datacapture.validation.NotValidated import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -76,7 +77,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) @@ -99,7 +100,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 4010a51ddb..1ccf9eb41e 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity +import com.google.android.fhir.datacapture.validation.NotValidated import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -76,7 +77,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) @@ -109,7 +110,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) @@ -132,7 +133,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { QuestionnaireItemViewItem( questionnaireItemComponent, QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = null, + validationResult = NotValidated, answersChangedCallback = { _, _, _ -> }, ) ) From 1a68298188385b53e5ddf49d42b545b7cc6c4073 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 07:45:53 +0700 Subject: [PATCH 60/75] spotlessApply --- .../views/QuestionnaireItemDatePickerViewHolderFactory.kt | 4 +--- .../QuestionnaireItemDateTimePickerViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemDialogSelectViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 4 ++-- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 6 +++--- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index fd6f71be1d..1c191757f1 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -21,16 +21,14 @@ import android.content.Context import android.icu.text.DateFormat import android.text.TextWatcher import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.view.ContextThemeWrapper import androidx.core.widget.doAfterTextChanged import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.utilities.isAndroidIcuSupported import com.google.android.fhir.datacapture.utilities.localizedString +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.textfield.TextInputEditText diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt index b0ce360813..da4b2fce70 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt @@ -27,10 +27,10 @@ import com.google.android.fhir.datacapture.utilities.localizedDateString import com.google.android.fhir.datacapture.utilities.localizedString import com.google.android.fhir.datacapture.utilities.toLocalizedString import com.google.android.fhir.datacapture.utilities.toLocalizedTimeString +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.textfield.TextInputEditText diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt index 484efe1e8d..32db59590b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDialogSelectViewHolderFactory.kt @@ -29,10 +29,10 @@ import com.google.android.fhir.datacapture.common.datatype.asStringValue import com.google.android.fhir.datacapture.displayString import com.google.android.fhir.datacapture.itemControl import com.google.android.fhir.datacapture.localizedTextSpanned +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.Job diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 10b00547e5..f5bc9c8f0c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -23,18 +23,18 @@ import android.view.View import android.view.View.FOCUS_DOWN import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager -import androidx.annotation.LayoutRes import android.widget.ImageView +import androidx.annotation.LayoutRes import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.localizedFlyoverSpanned +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 62d28d317f..af128cc9ac 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -21,12 +21,12 @@ import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.validation.Invalid -import com.google.android.fhir.datacapture.validation.NotValidated -import com.google.android.fhir.datacapture.validation.Valid import com.google.android.fhir.datacapture.fetchBitmap import com.google.android.fhir.datacapture.itemImage import com.google.android.fhir.datacapture.utilities.tryUnwrapContext +import com.google.android.fhir.datacapture.validation.Invalid +import com.google.android.fhir.datacapture.validation.NotValidated +import com.google.android.fhir.datacapture.validation.Valid import com.google.android.fhir.datacapture.validation.ValidationResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch From d1362209170cf1b1220c3719c59872367abb375a Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 08:06:04 +0700 Subject: [PATCH 61/75] Add item image to multi line edit text --- .../questionnaire_item_edit_text_multi_line_view.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml index e647c7733e..de2043602a 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml @@ -30,6 +30,16 @@ android:layout_marginBottom="@dimen/padding_default" /> + + Date: Sun, 21 Aug 2022 13:16:59 +0700 Subject: [PATCH 62/75] Trigger checks From f711a356d8ca542f7355f4e4fb22aefe85976284 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 14:41:10 +0700 Subject: [PATCH 63/75] Use itemMedia instead itemImage --- ...isplayViewHolderFactoryInstrumentedTest.kt | 40 ++++++++++++++++--- ...leLineViewHolderFactoryInstrumentedTest.kt | 40 ++++++++++++++++--- ...mGroupViewHolderFactoryInstrumentedTest.kt | 19 ++++----- .../MoreQuestionnaireItemComponents.kt | 19 +++++---- ...estionnaireItemDisplayViewHolderFactory.kt | 4 +- ...stionnaireItemEditTextViewHolderFactory.kt | 4 +- ...QuestionnaireItemGroupViewHolderFactory.kt | 4 +- 7 files changed, 90 insertions(+), 40 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 87b0beb5ba..7dbf747d06 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -23,6 +23,7 @@ import android.widget.ImageView import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity import com.google.android.fhir.datacapture.validation.NotValidated @@ -57,7 +58,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { } @Test - fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + fun shouldShowImage_whenItemMediaExtensionIsSet_withImageContentType() = runBlocking { val attachment = Attachment().apply { data = @@ -67,10 +68,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) } runOnUI { viewHolder.bind( @@ -90,7 +88,37 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { } @Test - fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "video/mp4" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index b9a89a1b20..98655e817e 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -23,6 +23,7 @@ import android.widget.ImageView import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity import com.google.android.fhir.datacapture.validation.NotValidated @@ -57,7 +58,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { } @Test - fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + fun shouldShowImage_whenItemMediaExtensionIsSet_witImageContentType() = runBlocking { val attachment = Attachment().apply { data = @@ -67,10 +68,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) } runOnUI { viewHolder.bind( @@ -90,7 +88,37 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { } @Test - fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "video/mp4" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + .isEqualTo(View.GONE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 1ccf9eb41e..5388b84c35 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -23,6 +23,7 @@ import android.widget.ImageView import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.TestActivity import com.google.android.fhir.datacapture.validation.NotValidated @@ -57,7 +58,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { } @Test - fun shouldShowImage_whenItemImageExtensionIsSet() = runBlocking { + fun shouldShowImage_whenItemMediaExtensionIsSet_withImageContentType() = runBlocking { val attachment = Attachment().apply { data = @@ -67,10 +68,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) } runOnUI { viewHolder.bind( @@ -90,20 +88,17 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { } @Test - fun shouldHideImageView_whenItemImageExtensionIsSet_withNonImageContentType() = runBlocking { + fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { val attachment = Attachment().apply { data = Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "document/pdf" + contentType = "video/mp4" } val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" - extension = - listOf( - Extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage", attachment) - ) + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) } runOnUI { viewHolder.bind( @@ -123,7 +118,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { } @Test - fun shouldHideImageView_whenItemImageExtensionIsNotSet() = runBlocking { + fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { text = "Kindly collect the reading as shown below in the figure" diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt index 9c541de8cf..ddc731254e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemComponents.kt @@ -19,10 +19,8 @@ package com.google.android.fhir.datacapture import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.text.Html.FROM_HTML_MODE_COMPACT import android.text.Spanned import android.util.Base64 -import android.util.Log import androidx.core.text.HtmlCompat import com.google.android.fhir.getLocalizedText import org.hl7.fhir.r4.model.Attachment @@ -34,6 +32,7 @@ import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType +import timber.log.Timber /** UI controls relevant to capturing question data. */ internal enum class ItemControlTypes( @@ -63,8 +62,8 @@ internal const val EXTENSION_ITEM_CONTROL_SYSTEM = "http://hl7.org/fhir/question internal const val EXTENSION_HIDDEN_URL = "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden" -internal const val EXTENSION_ITEM_IMAGE = - "http://hl7.org/fhir/uv/sdc/StructureDefinition/cpg-itemImage" +internal const val EXTENSION_ITEM_MEDIA = + "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia" internal const val EXTENSION_ENTRY_FORMAT_URL = "http://hl7.org/fhir/StructureDefinition/entryFormat" @@ -295,10 +294,10 @@ fun QuestionnaireResponse.QuestionnaireResponseItemComponent.addNestedItemsToAns private inline fun Questionnaire.QuestionnaireItemComponent.getNestedQuestionnaireResponseItems() = item.map { it.createQuestionnaireResponseItem() } -/** The Attachment defined in the [EXTENSION_ITEM_IMAGE] extension where applicable */ -internal val Questionnaire.QuestionnaireItemComponent.itemImage: Attachment? +/** The Attachment defined in the [EXTENSION_ITEM_MEDIA] extension where applicable */ +internal val Questionnaire.QuestionnaireItemComponent.itemMedia: Attachment? get() { - val extension = this.extension.singleOrNull { it.url == EXTENSION_ITEM_IMAGE } + val extension = this.extension.singleOrNull { it.url == EXTENSION_ITEM_MEDIA } return if (extension != null) extension.value as Attachment else null } @@ -316,7 +315,7 @@ fun Binary.getBitmap(): Bitmap? { BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) } } else { - Log.e("Binary", "Binary does not have a contentType image") + Timber.e("Binary does not have a contentType image") null } } @@ -333,7 +332,7 @@ suspend fun Attachment.fetchBitmap(context: Context): Bitmap? { if (isImage) { return BitmapFactory.decodeByteArray(data, 0, data.size) } - Log.e("Attachment", "Attachment is not of contentType image/**") + Timber.e("Attachment of contentType ${this.contentType} is not supported") return null } else if (url != null && (url.startsWith("https") || url.startsWith("http"))) { // Points to a Binary resource on a FHIR compliant server @@ -345,6 +344,6 @@ suspend fun Attachment.fetchBitmap(context: Context): Bitmap? { } } - Log.e("Attachment", "Could not determine the Bitmap in Attachment $id") + Timber.e("Could not determine the Bitmap in Attachment $id") return null } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index a4fbecf0ab..500b2535df 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -21,7 +21,7 @@ import android.widget.ImageView import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemImage +import com.google.android.fhir.datacapture.itemMedia import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult import kotlinx.coroutines.Dispatchers @@ -45,7 +45,7 @@ internal object QuestionnaireItemDisplayViewHolderFactory : itemImageView.setImageBitmap(null) - questionnaireItemViewItem.questionnaireItem.itemImage?.let { + questionnaireItemViewItem.questionnaireItem.itemMedia?.let { val activity = itemImageView.context.tryUnwrapContext()!! activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index f5bc9c8f0c..7a542e240d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -29,7 +29,7 @@ import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemImage +import com.google.android.fhir.datacapture.itemMedia import com.google.android.fhir.datacapture.localizedFlyoverSpanned import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid @@ -104,7 +104,7 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT // images from previous questions itemImageView.setImageBitmap(null) - questionnaireItemViewItem.questionnaireItem.itemImage?.let { + questionnaireItemViewItem.questionnaireItem.itemMedia?.let { val activity = itemImageView.context.tryUnwrapContext()!! activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index af128cc9ac..4d7647eb2b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -22,7 +22,7 @@ import android.widget.TextView import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemImage +import com.google.android.fhir.datacapture.itemMedia import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated @@ -51,7 +51,7 @@ internal object QuestionnaireItemGroupViewHolderFactory : itemImageView.setImageBitmap(null) - questionnaireItemViewItem.questionnaireItem.itemImage?.let { + questionnaireItemViewItem.questionnaireItem.itemMedia?.let { val activity = itemImageView.context.tryUnwrapContext()!! activity.lifecycleScope.launch(Dispatchers.IO) { it.fetchBitmap(itemImageView.context)?.run { From 03d52d132cbab1688ca312a89f43be15c8538e0e Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Sun, 21 Aug 2022 15:51:44 +0700 Subject: [PATCH 64/75] Trigger checks From bb2efddb947a6b599e83669955dc407478474bc3 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Tue, 23 Aug 2022 19:47:32 +0700 Subject: [PATCH 65/75] Revert unused dependency --- common/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 93ce016248..7086f361b7 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -29,7 +29,6 @@ configurations { all { exclude(module = "xpp3") } } dependencies { api(Dependencies.HapiFhir.structuresR4) - implementation(Dependencies.Androidx.appCompat) implementation(Dependencies.fhirUcum) testImplementation(Dependencies.Kotlin.kotlinTestJunit) From caf5e05120cc5c47e9e863959e978e6b2e986833 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Tue, 23 Aug 2022 19:48:27 +0700 Subject: [PATCH 66/75] Revert contentType of video to document --- ...QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 2 +- ...reItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt | 2 +- .../QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 7dbf747d06..bf6eeacf88 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -93,7 +93,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { Attachment().apply { data = Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "video/mp4" + contentType = "document/pdf" } val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 98655e817e..46e7e264db 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -93,7 +93,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { Attachment().apply { data = Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "video/mp4" + contentType = "document/pdf" } val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 5388b84c35..b5aefeb5ec 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -93,7 +93,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { Attachment().apply { data = Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "video/mp4" + contentType = "document/pdf" } val questionnaireItemComponent = Questionnaire.QuestionnaireItemComponent().apply { From c63c449f196bcdb78dfc39a63b70d55843538c30 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Mon, 5 Sep 2022 17:03:22 +0700 Subject: [PATCH 67/75] Rename ID from itemImage to item_image --- ...tionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt | 6 +++--- ...emEditTextSingleLineViewHolderFactoryInstrumentedTest.kt | 6 +++--- ...estionnaireItemGroupViewHolderFactoryInstrumentedTest.kt | 6 +++--- .../views/QuestionnaireItemEditTextViewHolderFactory.kt | 2 +- .../views/QuestionnaireItemGroupViewHolderFactory.kt | 2 +- .../layout/questionnaire_item_edit_text_multi_line_view.xml | 2 +- .../questionnaire_item_edit_text_single_line_view.xml | 2 +- .../res/layout/questionnaire_item_group_header_view.xml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index bf6eeacf88..f688ecdbb7 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -83,7 +83,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.VISIBLE) } @@ -113,7 +113,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } @@ -136,7 +136,7 @@ class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index 46e7e264db..b9581f3565 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -83,7 +83,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.VISIBLE) } @@ -113,7 +113,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } @@ -136,7 +136,7 @@ class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index b5aefeb5ec..26fe031d5d 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -83,7 +83,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.VISIBLE) } @@ -113,7 +113,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } @@ -136,7 +136,7 @@ class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { delay(1000) - assertThat(viewHolder.itemView.findViewById(R.id.itemImage).visibility) + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) .isEqualTo(View.GONE) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 7a542e240d..4fc508a3e9 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -60,7 +60,7 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT override fun init(itemView: View) { header = itemView.findViewById(R.id.header) - itemImageView = itemView.findViewById(R.id.itemImage) + itemImageView = itemView.findViewById(R.id.item_image) textInputLayout = itemView.findViewById(R.id.text_input_layout) textInputEditText = itemView.findViewById(R.id.text_input_edit_text) textInputEditText.setRawInputType(rawInputType) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 4d7647eb2b..0a9100f02c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -43,7 +43,7 @@ internal object QuestionnaireItemGroupViewHolderFactory : override fun init(itemView: View) { header = itemView.findViewById(R.id.header) error = itemView.findViewById(R.id.error) - itemImageView = itemView.findViewById(R.id.itemImage) + itemImageView = itemView.findViewById(R.id.item_image) } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { diff --git a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml index de2043602a..da83e93067 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml @@ -32,7 +32,7 @@ Date: Wed, 7 Sep 2022 06:32:34 +0700 Subject: [PATCH 68/75] Create QuestionnaireItemMediaView --- .../views/QuestionnaireItemMediaViewTest.kt | 124 ++++++++++++++++++ .../views/QuestionnaireItemMediaView.kt | 62 +++++++++ .../res/layout/questionnaire_item_media.xml | 19 +++ 3 files changed, 205 insertions(+) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt create mode 100644 datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaView.kt create mode 100644 datacapture/src/main/res/layout/questionnaire_item_media.xml diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt new file mode 100644 index 0000000000..23e64678fe --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.util.Base64 +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Questionnaire +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QuestionnaireItemMediaViewTest { + + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + + private lateinit var parent: FrameLayout + private lateinit var view: QuestionnaireItemMediaView + + @Before + fun setUp() { + activityScenarioRule.scenario.onActivity { activity -> parent = FrameLayout(activity) } + view = QuestionnaireItemMediaView(parent.context, null) + setTestLayout(view) + } + + @Test + fun shouldShowImage_whenItemMediaExtensionIsSet_withImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + + runOnUI { view.bind(questionnaireItemComponent) } + + delay(1000) + + assertThat(view.findViewById(R.id.item_image).visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "document/pdf" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + + runOnUI { view.bind(questionnaireItemComponent) } + + delay(1000) + + assertThat(view.findViewById(R.id.item_image).visibility).isEqualTo(View.GONE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + + runOnUI { view.bind(questionnaireItemComponent) } + + delay(1000) + + assertThat(view.findViewById(R.id.item_image).visibility).isEqualTo(View.GONE) + } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.scenario.onActivity { action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.scenario.onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaView.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaView.kt new file mode 100644 index 0000000000..ee20ce5f4b --- /dev/null +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaView.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.lifecycle.lifecycleScope +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.fetchBitmap +import com.google.android.fhir.datacapture.isImage +import com.google.android.fhir.datacapture.itemMedia +import com.google.android.fhir.datacapture.utilities.tryUnwrapContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.Questionnaire + +class QuestionnaireItemMediaView(context: Context, attrs: AttributeSet?) : + LinearLayout(context, attrs) { + + init { + LayoutInflater.from(context).inflate(R.layout.questionnaire_item_media, this, true) + } + + private var itemImage: ImageView = findViewById(R.id.item_image) + + fun bind(questionnaireItem: Questionnaire.QuestionnaireItemComponent) { + itemImage.setImageBitmap(null) + + questionnaireItem.itemMedia?.let { + val activity = context.tryUnwrapContext()!! + + activity.lifecycleScope.launch(Dispatchers.IO) { + if (it.isImage) { + it.fetchBitmap(itemImage.context)?.run { + launch(Dispatchers.Main) { + itemImage.visibility = View.VISIBLE + itemImage.setImageBitmap(this@run) + } + } + } + } + } + } +} diff --git a/datacapture/src/main/res/layout/questionnaire_item_media.xml b/datacapture/src/main/res/layout/questionnaire_item_media.xml new file mode 100644 index 0000000000..084b56ae5d --- /dev/null +++ b/datacapture/src/main/res/layout/questionnaire_item_media.xml @@ -0,0 +1,19 @@ + + + + + + From b072dfe7638eaed40d6f877525a8c2a8bfa99864 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 7 Sep 2022 06:33:36 +0700 Subject: [PATCH 69/75] Migrate ImageView to QuestionnaireItemMediaView --- ...estionnaireItemDisplayViewHolderFactory.kt | 26 ++--------------- ...stionnaireItemEditTextViewHolderFactory.kt | 28 ++----------------- ...QuestionnaireItemGroupViewHolderFactory.kt | 26 ++--------------- .../questionnaire_item_display_view.xml | 10 ++----- ...onnaire_item_edit_text_multi_line_view.xml | 10 ++----- ...nnaire_item_edit_text_single_line_view.xml | 10 ++----- .../questionnaire_item_group_header_view.xml | 10 ++----- 7 files changed, 21 insertions(+), 99 deletions(-) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index 500b2535df..7d27969f2b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -17,45 +17,25 @@ package com.google.android.fhir.datacapture.views import android.view.View -import android.widget.ImageView -import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemMedia -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.ValidationResult -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch internal object QuestionnaireItemDisplayViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_display_view) { override fun getQuestionnaireItemViewHolderDelegate() = object : QuestionnaireItemViewHolderDelegate { private lateinit var header: QuestionnaireItemHeaderView - private lateinit var itemImageView: ImageView + private lateinit var itemMedia: QuestionnaireItemMediaView override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem override fun init(itemView: View) { header = itemView.findViewById(R.id.header) - itemImageView = itemView.findViewById(R.id.itemImage) + itemMedia = itemView.findViewById(R.id.item_media) } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) - - itemImageView.setImageBitmap(null) - - questionnaireItemViewItem.questionnaireItem.itemMedia?.let { - val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch(Dispatchers.IO) { - it.fetchBitmap(itemImageView.context)?.run { - activity.lifecycleScope.launch(Dispatchers.Main) { - itemImageView.visibility = View.VISIBLE - itemImageView.setImageBitmap(this@run) - } - } - } - } + itemMedia.bind(questionnaireItemViewItem.questionnaireItem) } override fun displayValidationResult(validationResult: ValidationResult) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 4fc508a3e9..71cac7e076 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -23,23 +23,16 @@ import android.view.View import android.view.View.FOCUS_DOWN import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager -import android.widget.ImageView import androidx.annotation.LayoutRes import androidx.core.widget.doAfterTextChanged -import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemMedia import com.google.android.fhir.datacapture.localizedFlyoverSpanned -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.hl7.fhir.r4.model.QuestionnaireResponse internal abstract class QuestionnaireItemEditTextViewHolderFactory( @@ -52,7 +45,7 @@ internal abstract class QuestionnaireItemEditTextViewHolderFactory( abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputType: Int) : QuestionnaireItemViewHolderDelegate { private lateinit var header: QuestionnaireItemHeaderView - private lateinit var itemImageView: ImageView + private lateinit var itemMedia: QuestionnaireItemMediaView private lateinit var textInputLayout: TextInputLayout private lateinit var textInputEditText: TextInputEditText override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem @@ -60,7 +53,7 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT override fun init(itemView: View) { header = itemView.findViewById(R.id.header) - itemImageView = itemView.findViewById(R.id.item_image) + itemMedia = itemView.findViewById(R.id.item_media) textInputLayout = itemView.findViewById(R.id.text_input_layout) textInputEditText = itemView.findViewById(R.id.text_input_edit_text) textInputEditText.setRawInputType(rawInputType) @@ -89,6 +82,7 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) + itemMedia.bind(questionnaireItemViewItem.questionnaireItem) textInputLayout.hint = questionnaireItemViewItem.questionnaireItem.localizedFlyoverSpanned textInputEditText.removeTextChangedListener(textWatcher) @@ -99,22 +93,6 @@ abstract class QuestionnaireItemEditTextViewHolderDelegate(private val rawInputT textWatcher = textInputEditText.doAfterTextChanged { editable: Editable? -> updateAnswer(editable) } - - // The RecyclerView is recycling the ImageView therefore making them visible and recycling - // images from previous questions - itemImageView.setImageBitmap(null) - - questionnaireItemViewItem.questionnaireItem.itemMedia?.let { - val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch(Dispatchers.IO) { - it.fetchBitmap(itemImageView.context)?.run { - activity.lifecycleScope.launch(Dispatchers.Main) { - itemImageView.visibility = View.VISIBLE - itemImageView.setImageBitmap(this@run) - } - } - } - } } private fun updateAnswer(editable: Editable?) { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index 0a9100f02c..8b8f6acccb 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -17,51 +17,31 @@ package com.google.android.fhir.datacapture.views import android.view.View -import android.widget.ImageView import android.widget.TextView -import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.fetchBitmap -import com.google.android.fhir.datacapture.itemMedia -import com.google.android.fhir.datacapture.utilities.tryUnwrapContext import com.google.android.fhir.datacapture.validation.Invalid import com.google.android.fhir.datacapture.validation.NotValidated import com.google.android.fhir.datacapture.validation.Valid import com.google.android.fhir.datacapture.validation.ValidationResult -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch internal object QuestionnaireItemGroupViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_group_header_view) { override fun getQuestionnaireItemViewHolderDelegate() = object : QuestionnaireItemViewHolderDelegate { private lateinit var header: QuestionnaireItemHeaderView - private lateinit var itemImageView: ImageView + private lateinit var itemMedia: QuestionnaireItemMediaView private lateinit var error: TextView override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem override fun init(itemView: View) { header = itemView.findViewById(R.id.header) + itemMedia = itemView.findViewById(R.id.item_media) error = itemView.findViewById(R.id.error) - itemImageView = itemView.findViewById(R.id.item_image) } override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { header.bind(questionnaireItemViewItem.questionnaireItem) - - itemImageView.setImageBitmap(null) - - questionnaireItemViewItem.questionnaireItem.itemMedia?.let { - val activity = itemImageView.context.tryUnwrapContext()!! - activity.lifecycleScope.launch(Dispatchers.IO) { - it.fetchBitmap(itemImageView.context)?.run { - activity.lifecycleScope.launch(Dispatchers.Main) { - itemImageView.visibility = View.VISIBLE - itemImageView.setImageBitmap(this@run) - } - } - } - } + itemMedia.bind(questionnaireItemViewItem.questionnaireItem) } override fun displayValidationResult(validationResult: ValidationResult) { diff --git a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml index 15a6507652..6b67643b23 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml @@ -29,14 +29,10 @@ android:layout_height="wrap_content" /> - diff --git a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml index da83e93067..235e9358f5 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_edit_text_multi_line_view.xml @@ -30,14 +30,10 @@ android:layout_marginBottom="@dimen/padding_default" /> - - - Date: Wed, 7 Sep 2022 06:40:47 +0700 Subject: [PATCH 70/75] Add test for QuestionnaireItemEditTextMultiLineViewHolderFactory --- ...tiLineViewHolderFactoryInstrumentedTest.kt | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt new file mode 100644 index 0000000000..3b087b184e --- /dev/null +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.views + +import android.util.Base64 +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA +import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.TestActivity +import com.google.android.fhir.datacapture.validation.NotValidated +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.Attachment +import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest { + + @Rule + @JvmField + var activityScenarioRule: ActivityScenarioRule = + ActivityScenarioRule(TestActivity::class.java) + + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setup() { + activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } + viewHolder = QuestionnaireItemEditTextMultiLineViewHolderFactory.create(parent) + setTestLayout(viewHolder.itemView) + } + + @Test + fun shouldShowImage_whenItemMediaExtensionIsSet_witImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "image/png" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) + .isEqualTo(View.VISIBLE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { + val attachment = + Attachment().apply { + data = + Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) + contentType = "document/pdf" + } + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) + .isEqualTo(View.GONE) + } + + @Test + fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { + val questionnaireItemComponent = + Questionnaire.QuestionnaireItemComponent().apply { + text = "Kindly collect the reading as shown below in the figure" + } + runOnUI { + viewHolder.bind( + QuestionnaireItemViewItem( + questionnaireItemComponent, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + } + + delay(1000) + + assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) + .isEqualTo(View.GONE) + } + + /** Method to run code snippet on UI/main thread */ + private fun runOnUI(action: () -> Unit) { + activityScenarioRule.getScenario().onActivity { activity -> action() } + } + + /** Method to set content view for test activity */ + private fun setTestLayout(view: View) { + activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} From 21ebb1184f400482d2b2245268bb15a21d776d2d Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Wed, 21 Sep 2022 09:37:15 +0700 Subject: [PATCH 71/75] Set display and group type questionnaire as vertical --- .../src/main/res/layout/questionnaire_item_display_view.xml | 2 +- .../main/res/layout/questionnaire_item_group_header_view.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml index 6b67643b23..813120e1be 100644 --- a/datacapture/src/main/res/layout/questionnaire_item_display_view.xml +++ b/datacapture/src/main/res/layout/questionnaire_item_display_view.xml @@ -20,7 +20,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/item_margin_horizontal" android:layout_marginTop="@dimen/padding_default" - android:orientation="horizontal" + android:orientation="vertical" > Date: Thu, 29 Sep 2022 20:07:32 +0700 Subject: [PATCH 72/75] Remove duplicate test --- ...isplayViewHolderFactoryInstrumentedTest.kt | 153 ------------------ ...tiLineViewHolderFactoryInstrumentedTest.kt | 153 ------------------ ...leLineViewHolderFactoryInstrumentedTest.kt | 153 ------------------ ...mGroupViewHolderFactoryInstrumentedTest.kt | 153 ------------------ 4 files changed, 612 deletions(-) delete mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt delete mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt delete mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt delete mode 100644 datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt deleted file mode 100644 index f688ecdbb7..0000000000 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.datacapture.views - -import android.util.Base64 -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA -import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.TestActivity -import com.google.android.fhir.datacapture.validation.NotValidated -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension -import org.hl7.fhir.r4.model.Questionnaire -import org.hl7.fhir.r4.model.QuestionnaireResponse -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { - - @Rule - @JvmField - var activityScenarioRule: ActivityScenarioRule = - ActivityScenarioRule(TestActivity::class.java) - - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setup() { - activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } - viewHolder = QuestionnaireItemDisplayViewHolderFactory.create(parent) - setTestLayout(viewHolder.itemView) - } - - @Test - fun shouldShowImage_whenItemMediaExtensionIsSet_withImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "document/pdf" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - /** Method to run code snippet on UI/main thread */ - private fun runOnUI(action: () -> Unit) { - activityScenarioRule.getScenario().onActivity { activity -> action() } - } - - /** Method to set content view for test activity */ - private fun setTestLayout(view: View) { - activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - } -} diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt deleted file mode 100644 index 3b087b184e..0000000000 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.datacapture.views - -import android.util.Base64 -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA -import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.TestActivity -import com.google.android.fhir.datacapture.validation.NotValidated -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension -import org.hl7.fhir.r4.model.Questionnaire -import org.hl7.fhir.r4.model.QuestionnaireResponse -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest { - - @Rule - @JvmField - var activityScenarioRule: ActivityScenarioRule = - ActivityScenarioRule(TestActivity::class.java) - - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setup() { - activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } - viewHolder = QuestionnaireItemEditTextMultiLineViewHolderFactory.create(parent) - setTestLayout(viewHolder.itemView) - } - - @Test - fun shouldShowImage_whenItemMediaExtensionIsSet_witImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "document/pdf" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - /** Method to run code snippet on UI/main thread */ - private fun runOnUI(action: () -> Unit) { - activityScenarioRule.getScenario().onActivity { activity -> action() } - } - - /** Method to set content view for test activity */ - private fun setTestLayout(view: View) { - activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - } -} diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt deleted file mode 100644 index b9581f3565..0000000000 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.datacapture.views - -import android.util.Base64 -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA -import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.TestActivity -import com.google.android.fhir.datacapture.validation.NotValidated -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension -import org.hl7.fhir.r4.model.Questionnaire -import org.hl7.fhir.r4.model.QuestionnaireResponse -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { - - @Rule - @JvmField - var activityScenarioRule: ActivityScenarioRule = - ActivityScenarioRule(TestActivity::class.java) - - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setup() { - activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } - viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) - setTestLayout(viewHolder.itemView) - } - - @Test - fun shouldShowImage_whenItemMediaExtensionIsSet_witImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "document/pdf" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - /** Method to run code snippet on UI/main thread */ - private fun runOnUI(action: () -> Unit) { - activityScenarioRule.getScenario().onActivity { activity -> action() } - } - - /** Method to set content view for test activity */ - private fun setTestLayout(view: View) { - activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - } -} diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt deleted file mode 100644 index 26fe031d5d..0000000000 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.datacapture.views - -import android.util.Base64 -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.android.fhir.datacapture.EXTENSION_ITEM_MEDIA -import com.google.android.fhir.datacapture.R -import com.google.android.fhir.datacapture.TestActivity -import com.google.android.fhir.datacapture.validation.NotValidated -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hl7.fhir.r4.model.Attachment -import org.hl7.fhir.r4.model.Extension -import org.hl7.fhir.r4.model.Questionnaire -import org.hl7.fhir.r4.model.QuestionnaireResponse -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { - - @Rule - @JvmField - var activityScenarioRule: ActivityScenarioRule = - ActivityScenarioRule(TestActivity::class.java) - - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setup() { - activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) } - viewHolder = QuestionnaireItemGroupViewHolderFactory.create(parent) - setTestLayout(viewHolder.itemView) - } - - @Test - fun shouldShowImage_whenItemMediaExtensionIsSet_withImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "image/png" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.VISIBLE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsSet_withNonImageContentType() = runBlocking { - val attachment = - Attachment().apply { - data = - Base64.decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", Base64.DEFAULT) - contentType = "document/pdf" - } - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - extension = listOf(Extension(EXTENSION_ITEM_MEDIA, attachment)) - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - @Test - fun shouldHideImageView_whenItemMediaExtensionIsNotSet() = runBlocking { - val questionnaireItemComponent = - Questionnaire.QuestionnaireItemComponent().apply { - text = "Kindly collect the reading as shown below in the figure" - } - runOnUI { - viewHolder.bind( - QuestionnaireItemViewItem( - questionnaireItemComponent, - QuestionnaireResponse.QuestionnaireResponseItemComponent(), - validationResult = NotValidated, - answersChangedCallback = { _, _, _ -> }, - ) - ) - } - - delay(1000) - - assertThat(viewHolder.itemView.findViewById(R.id.item_image).visibility) - .isEqualTo(View.GONE) - } - - /** Method to run code snippet on UI/main thread */ - private fun runOnUI(action: () -> Unit) { - activityScenarioRule.getScenario().onActivity { activity -> action() } - } - - /** Method to set content view for test activity */ - private fun setTestLayout(view: View) { - activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) } - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - } -} From 2f8814eba952bbb2436828aaa0965761920c0449 Mon Sep 17 00:00:00 2001 From: fikrimilano Date: Thu, 29 Sep 2022 20:08:10 +0700 Subject: [PATCH 73/75] Rename test class --- ...iewTest.kt => QuestionnaireItemMediaViewInstrumentedTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/{QuestionnaireItemMediaViewTest.kt => QuestionnaireItemMediaViewInstrumentedTest.kt} (98%) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewInstrumentedTest.kt similarity index 98% rename from datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt rename to datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewInstrumentedTest.kt index 23e64678fe..52508725a5 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemMediaViewInstrumentedTest.kt @@ -38,7 +38,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class QuestionnaireItemMediaViewTest { +class QuestionnaireItemMediaViewInstrumentedTest { @Rule @JvmField From 2e9c0dea7f60c12811c567b1f80c52d2ce3325df Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Fri, 7 Oct 2022 16:04:39 +0300 Subject: [PATCH 74/75] Catch exception in flow Signed-off-by: Elly Kitoto --- .../android/fhir/impl/FhirEngineImpl.kt | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt b/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt index 3bb878c0b9..896cb41c2d 100644 --- a/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt +++ b/engine/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt @@ -34,7 +34,9 @@ import com.google.android.fhir.sync.Resolved import com.google.android.fhir.toTimeZoneString import java.time.OffsetDateTime import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType @@ -87,12 +89,13 @@ internal class FhirEngineImpl(private val database: Database, private val contex conflictResolver: ConflictResolver, download: suspend (SyncDownloadContext) -> Flow> ) { + download( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = database.lastUpdate(type) - } - ) - .collect { resources -> + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = database.lastUpdate(type) + } + ) + .onEach { resources -> database.withTransaction { val resolved = resolveConflictingResources( @@ -104,6 +107,10 @@ internal class FhirEngineImpl(private val database: Database, private val contex saveResolvedResourcesToDatabase(resolved) } } + .catch { throwable: Throwable -> + Timber.e(throwable, "Error saving remote resource to database") + } + .collect() } private suspend fun saveResolvedResourcesToDatabase(resolved: List?) { @@ -115,9 +122,11 @@ internal class FhirEngineImpl(private val database: Database, private val contex private suspend fun saveRemoteResourcesToDatabase(resources: List) { val timeStamps = - resources.groupBy { it.resourceType }.entries.map { - SyncedResourceEntity(it.key, it.value.maxOf { it.meta.lastUpdated }.toTimeZoneString()) - } + resources + .groupBy { it.resourceType } + .entries.map { + SyncedResourceEntity(it.key, it.value.maxOf { it.meta.lastUpdated }.toTimeZoneString()) + } database.insertSyncedResources(timeStamps, resources) } @@ -207,7 +216,8 @@ internal class FhirEngineImpl(private val database: Database, private val contex */ private val Bundle.BundleEntryResponseComponent.resourceIdAndType: Pair? get() = - location?.split("/")?.takeIf { it.size > 3 }?.let { - it[it.size - 3] to ResourceType.fromCode(it[it.size - 4]) - } + location + ?.split("/") + ?.takeIf { it.size > 3 } + ?.let { it[it.size - 3] to ResourceType.fromCode(it[it.size - 4]) } } From f9545f7fae8997234eba9893000cee054a698d6e Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Fri, 7 Oct 2022 16:06:37 +0300 Subject: [PATCH 75/75] Fix flow exception violation issue Signed-off-by: Elly Kitoto --- .../fhir/demo/data/DownloadWorkManagerImpl.kt | 11 +- .../android/fhir/sync/DownloadWorkManager.kt | 5 +- .../fhir/sync/download/DownloaderImpl.kt | 43 +++--- .../ResourceParamsBasedDownloadWorkManager.kt | 10 +- .../android/fhir/resource/TestingUtils.kt | 18 ++- ...ourceParamsBasedDownloadWorkManagerTest.kt | 140 +++++++++--------- 6 files changed, 123 insertions(+), 104 deletions(-) diff --git a/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt b/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt index b235951743..cbc6ab07cd 100644 --- a/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt +++ b/demo/src/main/java/com/google/android/fhir/demo/data/DownloadWorkManagerImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,16 +30,17 @@ import org.hl7.fhir.r4.model.ResourceType class DownloadWorkManagerImpl : DownloadWorkManager { private val resourceTypeList = ResourceType.values().map { it.name } private val urls = LinkedList(listOf("Patient?address-city=NAIROBI")) + override var nextRequestUrl: String? = null override suspend fun getNextRequestUrl(context: SyncDownloadContext): String? { - var url = urls.poll() ?: return null + nextRequestUrl = urls.poll() ?: return null val resourceTypeToDownload = - ResourceType.fromCode(url.findAnyOf(resourceTypeList, ignoreCase = true)!!.second) + ResourceType.fromCode(nextRequestUrl?.findAnyOf(resourceTypeList, ignoreCase = true)!!.second) context.getLatestTimestampFor(resourceTypeToDownload)?.let { - url = affixLastUpdatedTimestamp(url!!, it) + nextRequestUrl = affixLastUpdatedTimestamp(nextRequestUrl!!, it) } - return url + return nextRequestUrl } override suspend fun processResponse(response: Resource): Collection { diff --git a/engine/src/main/java/com/google/android/fhir/sync/DownloadWorkManager.kt b/engine/src/main/java/com/google/android/fhir/sync/DownloadWorkManager.kt index 994f99fc9d..2144e4c277 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/DownloadWorkManager.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/DownloadWorkManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,9 @@ import org.hl7.fhir.r4.model.Resource * manager be created or should there be an API to restart a new download job. */ interface DownloadWorkManager { + + var nextRequestUrl: String? + /** * Returns the URL for the next download request, or `null` if there is no more download request * to be issued. diff --git a/engine/src/main/java/com/google/android/fhir/sync/download/DownloaderImpl.kt b/engine/src/main/java/com/google/android/fhir/sync/download/DownloaderImpl.kt index d66b0dc73b..d244a9d20f 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/download/DownloaderImpl.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/download/DownloaderImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import com.google.android.fhir.sync.DownloadWorkManager import com.google.android.fhir.sync.Downloader import com.google.android.fhir.sync.ResourceSyncException import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import org.hl7.fhir.r4.model.ResourceType @@ -38,25 +39,29 @@ internal class DownloaderImpl( ) : Downloader { private val resourceTypeList = ResourceType.values().map { it.name } - override suspend fun download(context: SyncDownloadContext): Flow = flow { - var resourceTypeToDownload: ResourceType = ResourceType.Bundle - emit(DownloadState.Started(resourceTypeToDownload)) - var url = downloadWorkManager.getNextRequestUrl(context) - while (url != null) { - try { - resourceTypeToDownload = - ResourceType.fromCode(url.findAnyOf(resourceTypeList, ignoreCase = true)!!.second) - - emit( - DownloadState.Success( - downloadWorkManager.processResponse(dataSource.download(url!!)).toList() + override suspend fun download(context: SyncDownloadContext): Flow = + flow { + val resourceTypeToDownload: ResourceType = ResourceType.Bundle + emit(DownloadState.Started(resourceTypeToDownload)) + var url = downloadWorkManager.getNextRequestUrl(context) + while (url != null) { + emit( + DownloadState.Success( + downloadWorkManager.processResponse(dataSource.download(url)).toList() + ) + ) + url = downloadWorkManager.getNextRequestUrl(context) + } + } + .catch { throwable: Throwable -> + val resourceTypeToDownload = + ResourceType.fromCode( + downloadWorkManager.nextRequestUrl + ?.findAnyOf(resourceTypeList, ignoreCase = true) + ?.second ) + emit( + DownloadState.Failure(ResourceSyncException(resourceTypeToDownload, Exception(throwable))) ) - } catch (exception: Exception) { - emit(DownloadState.Failure(ResourceSyncException(resourceTypeToDownload, exception))) } - - url = downloadWorkManager.getNextRequestUrl(context) - } - } } diff --git a/engine/src/main/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManager.kt b/engine/src/main/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManager.kt index 0ae5b55bdc..39d7a9001e 100644 --- a/engine/src/main/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManager.kt +++ b/engine/src/main/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManager.kt @@ -38,6 +38,7 @@ typealias ResourceSearchParams = Map */ class ResourceParamsBasedDownloadWorkManager(syncParams: ResourceSearchParams) : DownloadWorkManager { + override var nextRequestUrl: String? = null private val resourcesToDownloadWithSearchParams = LinkedList(syncParams.entries) private val urlOfTheNextPagesToDownloadForAResource = LinkedList() @@ -57,7 +58,8 @@ class ResourceParamsBasedDownloadWorkManager(syncParams: ResourceSearchParams) : } } - "${resourceType.name}?${newParams.concatParams()}" + nextRequestUrl = "${resourceType.name}?${newParams.concatParams()}" + nextRequestUrl } } @@ -67,9 +69,9 @@ class ResourceParamsBasedDownloadWorkManager(syncParams: ResourceSearchParams) : } return if (response is Bundle && response.type == Bundle.BundleType.SEARCHSET) { - response.link.firstOrNull { component -> component.relation == "next" }?.url?.let { next -> - urlOfTheNextPagesToDownloadForAResource.add(next) - } + response.link + .firstOrNull { component -> component.relation == "next" } + ?.url?.let { next -> urlOfTheNextPagesToDownloadForAResource.add(next) } response.entry.map { it.resource } } else { diff --git a/engine/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt b/engine/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt index 87c2880c92..d42cb05689 100644 --- a/engine/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt +++ b/engine/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt @@ -31,7 +31,6 @@ import java.time.OffsetDateTime import java.util.Date import java.util.LinkedList import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Meta import org.hl7.fhir.r4.model.Patient @@ -104,7 +103,12 @@ class TestingUtils constructor(private val iParser: IParser) { ) : DownloadWorkManager { private val urls = LinkedList(queries) - override suspend fun getNextRequestUrl(context: SyncDownloadContext): String? = urls.poll() + override var nextRequestUrl: String? = null + + override suspend fun getNextRequestUrl(context: SyncDownloadContext): String? { + nextRequestUrl = urls.poll() + return nextRequestUrl + } override suspend fun processResponse(response: Resource): Collection { val patient = Patient().setMeta(Meta().setLastUpdated(Date())) @@ -142,12 +146,12 @@ class TestingUtils constructor(private val iParser: IParser) { download: suspend (SyncDownloadContext) -> Flow> ) { download( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType): String { - return "123456788" + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType): String { + return "123456788" + } } - } - ) + ) .collect {} } override suspend fun count(search: Search): Long { diff --git a/engine/src/test/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManagerTest.kt b/engine/src/test/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManagerTest.kt index ff6acdc43d..f94651fbdd 100644 --- a/engine/src/test/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManagerTest.kt +++ b/engine/src/test/java/com/google/android/fhir/sync/download/ResourceParamsBasedDownloadWorkManagerTest.kt @@ -115,87 +115,91 @@ class ResourceParamsBasedDownloadWorkManagerTest { @Test fun getNextRequestUrl_withLastUpdatedTimeProvidedInContext_ShouldAppendGtPrefixToLastUpdatedSearchParam() = - runBlockingTest { - val downloadManager = - ResourceParamsBasedDownloadWorkManager(mapOf(ResourceType.Patient to emptyMap())) - val url = - downloadManager.getNextRequestUrl( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-06-28" - } - ) - assertThat(url).isEqualTo("Patient?_sort=_lastUpdated&_lastUpdated=gt2022-06-28") - } + runBlockingTest { + val downloadManager = + ResourceParamsBasedDownloadWorkManager(mapOf(ResourceType.Patient to emptyMap())) + val url = + downloadManager.getNextRequestUrl( + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-06-28" + } + ) + assertThat(url).isEqualTo("Patient?_sort=_lastUpdated&_lastUpdated=gt2022-06-28") + } @Test fun getNextRequestUrl_withLastUpdatedSyncParamProvided_shouldReturnUrlWithExactProvidedLastUpdatedSyncParam() = - runBlockingTest { - val downloadManager = - ResourceParamsBasedDownloadWorkManager( - mapOf( - ResourceType.Patient to - mapOf( - SyncDataParams.LAST_UPDATED_KEY to "2022-06-28", - SyncDataParams.SORT_KEY to "status" - ) + runBlockingTest { + val downloadManager = + ResourceParamsBasedDownloadWorkManager( + mapOf( + ResourceType.Patient to + mapOf( + SyncDataParams.LAST_UPDATED_KEY to "2022-06-28", + SyncDataParams.SORT_KEY to "status" + ) + ) ) - ) - val url = - downloadManager.getNextRequestUrl( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-07-07" - } - ) - assertThat(url).isEqualTo("Patient?_lastUpdated=2022-06-28&_sort=status") - } + val url = + downloadManager.getNextRequestUrl( + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-07-07" + } + ) + assertThat(url).isEqualTo("Patient?_lastUpdated=2022-06-28&_sort=status") + } @Test fun getNextRequestUrl_withLastUpdatedSyncParamHavingGtPrefix_shouldReturnUrlWithExactProvidedLastUpdatedSyncParam() = - runBlockingTest { - val downloadManager = - ResourceParamsBasedDownloadWorkManager( - mapOf(ResourceType.Patient to mapOf(SyncDataParams.LAST_UPDATED_KEY to "gt2022-06-28")) - ) - val url = - downloadManager.getNextRequestUrl( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-07-07" - } - ) - assertThat(url).isEqualTo("Patient?_lastUpdated=gt2022-06-28&_sort=_lastUpdated") - } + runBlockingTest { + val downloadManager = + ResourceParamsBasedDownloadWorkManager( + mapOf(ResourceType.Patient to mapOf(SyncDataParams.LAST_UPDATED_KEY to "gt2022-06-28")) + ) + val url = + downloadManager.getNextRequestUrl( + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = "2022-07-07" + } + ) + assertThat(url).isEqualTo("Patient?_lastUpdated=gt2022-06-28&_sort=_lastUpdated") + } @Test fun getNextRequestUrl_withNullUpdatedTimeStamp_shouldReturnUrlWithoutLastUpdatedQueryParam() = - runBlockingTest { - val downloadManager = - ResourceParamsBasedDownloadWorkManager( - mapOf(ResourceType.Patient to mapOf(Patient.ADDRESS_CITY.paramName to "NAIROBI")) - ) - val actual = - downloadManager.getNextRequestUrl( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = null - } - ) - assertThat(actual).isEqualTo("Patient?address-city=NAIROBI&_sort=_lastUpdated") - } + runBlockingTest { + val downloadManager = + ResourceParamsBasedDownloadWorkManager( + mapOf(ResourceType.Patient to mapOf(Patient.ADDRESS_CITY.paramName to "NAIROBI")) + ) + val actual = + downloadManager.getNextRequestUrl( + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = null + } + ) + val expectedUrl = "Patient?address-city=NAIROBI&_sort=_lastUpdated" + assertThat(downloadManager.nextRequestUrl).isEqualTo(expectedUrl) + assertThat(actual).isEqualTo(expectedUrl) + } @Test fun getNextRequestUrl_withEmptyUpdatedTimeStamp_shouldReturnUrlWithoutLastUpdatedQueryParam() = - runBlockingTest { - val downloadManager = - ResourceParamsBasedDownloadWorkManager( - mapOf(ResourceType.Patient to mapOf(Patient.ADDRESS_CITY.paramName to "NAIROBI")) - ) - val actual = - downloadManager.getNextRequestUrl( - object : SyncDownloadContext { - override suspend fun getLatestTimestampFor(type: ResourceType) = "" - } - ) - assertThat(actual).isEqualTo("Patient?address-city=NAIROBI&_sort=_lastUpdated") - } + runBlockingTest { + val downloadManager = + ResourceParamsBasedDownloadWorkManager( + mapOf(ResourceType.Patient to mapOf(Patient.ADDRESS_CITY.paramName to "NAIROBI")) + ) + val actual = + downloadManager.getNextRequestUrl( + object : SyncDownloadContext { + override suspend fun getLatestTimestampFor(type: ResourceType) = "" + } + ) + val expectedUrl = "Patient?address-city=NAIROBI&_sort=_lastUpdated" + assertThat(downloadManager.nextRequestUrl).isEqualTo(expectedUrl) + assertThat(actual).isEqualTo(expectedUrl) + } @Test fun processResponse_withBundleTypeSearchSet_shouldReturnPatient() = runBlockingTest {