Skip to content

Commit

Permalink
Merge branch 'master' into sp/cancel-job
Browse files Browse the repository at this point in the history
  • Loading branch information
jingtang10 authored Jan 27, 2025
2 parents 59aab15 + ecf949a commit c911cb2
Show file tree
Hide file tree
Showing 86 changed files with 5,307 additions and 844 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Releases.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ object Releases {

object Engine : LibraryArtifact {
override val artifactId = "engine"
override val version = "1.0.0"
override val version = "1.1.0"
override val name = "Android FHIR Engine Library"
}

Expand Down
36 changes: 36 additions & 0 deletions catalog/src/main/assets/component_time_picker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "1",
"text": "Enter a time",
"type": "time",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/entryFormat",
"valueString": "hh-mm"
}
],
"item": [
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
"valueCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/questionnaire-display-category",
"code": "instructions"
}
]
}
}
],
"linkId": "1-most-recent",
"text": "Use keyboard entry or time picker",
"type": "display"
}
]
}
]
}
37 changes: 37 additions & 0 deletions catalog/src/main/assets/component_time_picker_with_validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "1",
"text": "Enter a time",
"type": "time",
"required": true,
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/entryFormat",
"valueString": "hh-mm"
}
],
"item": [
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
"valueCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/questionnaire-display-category",
"code": "instructions"
}
]
}
}
],
"linkId": "1-most-recent",
"text": "Use keyboard entry or time picker",
"type": "display"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ class ComponentListViewModel(application: Application, private val state: SavedS
"component_date_picker.json",
"component_date_picker_with_validation.json",
),
TIME_PICKER(
R.drawable.ic_timepicker,
R.string.component_name_time_picker,
"component_time_picker.json",
"component_time_picker_with_validation.json",
),
DATE_TIME_PICKER(
R.drawable.ic_timepicker,
R.string.component_name_date_time_picker,
Expand Down Expand Up @@ -171,6 +177,7 @@ class ComponentListViewModel(application: Application, private val state: SavedS
ViewItem.ComponentItem(Component.TEXT_FIELD),
ViewItem.ComponentItem(Component.AUTO_COMPLETE),
ViewItem.ComponentItem(Component.DATE_PICKER),
ViewItem.ComponentItem(Component.TIME_PICKER),
ViewItem.ComponentItem(Component.DATE_TIME_PICKER),
ViewItem.ComponentItem(Component.SLIDER),
ViewItem.ComponentItem(Component.QUANTITY),
Expand Down
1 change: 1 addition & 0 deletions catalog/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<string name="component_name_text_field">Text field </string>
<string name="component_name_auto_complete">Auto Complete</string>
<string name="component_name_date_picker">Date picker</string>
<string name="component_name_time_picker">Time picker</string>
<string name="component_name_date_time_picker">DateTime picker</string>
<string name="component_name_slider">Slider</string>
<string name="component_name_quantity">Quantity</string>
Expand Down
20 changes: 10 additions & 10 deletions codelabs/datacapture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ of the `app/build.gradle.kts` file of your project:
dependencies {
// ...

implementation("com.google.android.fhir:data-capture:1.0.0")
implementation("androidx.fragment:fragment-ktx:1.5.5")
implementation("com.google.android.fhir:data-capture:1.2.0")
implementation("androidx.fragment:fragment-ktx:1.6.0")
}
```

Expand Down Expand Up @@ -150,11 +150,11 @@ Open `MainActivity.kt` and add the following code to the `MainActivity` class:

```kotlin
// Step 2: Configure a QuestionnaireFragment
val questionnaireJsonString = getStringFromAssets("questionnaire.json")
questionnaireJsonString = getStringFromAssets("questionnaire.json")

val questionnaireFragment =
QuestionnaireFragment.builder().setQuestionnaire(questionnaireJsonString!!).build()

val questionnaireParams = bundleOf(
QuestionnaireFragment.EXTRA_QUESTIONNAIRE_JSON_STRING to questionnaireJsonString
)
```

### Step 3: Add the QuestionnaireFragment to the FragmentContainerView
Expand All @@ -168,10 +168,10 @@ Add the following code to the `MainActivity` class:
```kotlin
// Step 3: Add the QuestionnaireFragment to the FragmentContainerView
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<QuestionnaireFragment>(R.id.fragment_container_view, args = questionnaireParams)
}
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fragment_container_view, questionnaireFragment)
}
}
// Submit button callback
supportFragmentManager.setFragmentResultListener(
Expand Down
10 changes: 8 additions & 2 deletions codelabs/engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ file of your project:
dependencies {
// ...
implementation("com.google.android.fhir:engine:1.0.0")
implementation("com.google.android.fhir:engine:1.1.0")
}
```
Expand Down Expand Up @@ -257,7 +257,13 @@ outlined below will guide you through the process.
override fun getFhirEngine() = FhirApplication.fhirEngine(applicationContext)
override fun getUploadStrategy() = UploadStrategy.AllChangesSquashedBundlePut
override fun getUploadStrategy() =
UploadStrategy.forBundleRequest(
methodForCreate = HttpCreateMethod.PUT,
methodForUpdate = HttpUpdateMethod.PATCH,
squash = true,
bundleSize = 500,
)
}
```
Expand Down
2 changes: 1 addition & 1 deletion codelabs/engine/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ dependencies {
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")

implementation("com.google.android.fhir:engine:1.0.0")
implementation("com.google.android.fhir:engine:1.1.0")
implementation("androidx.fragment:fragment-ktx:1.8.3")
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2023-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,8 +30,10 @@ class PatientItemViewHolder(binding: PatientListItemViewBinding) :

fun bind(patientItem: Patient) {
nameTextView.text =
patientItem.name.first().let { it.given.joinToString(separator = " ") + " " + it.family }
genderTextView.text = patientItem.gender.display
patientItem.name.firstOrNull()?.let {
it.given.joinToString(separator = " ") + " " + it.family
}
genderTextView.text = patientItem.gender?.display
cityTextView.text = patientItem.address.singleOrNull()?.city
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemView
import com.google.android.fhir.datacapture.views.factories.RadioGroupViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.RepeatedGroupHeaderItemViewHolder
import com.google.android.fhir.datacapture.views.factories.SliderViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.TimePickerViewHolderFactory
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType

internal class QuestionnaireEditAdapter(
Expand Down Expand Up @@ -103,6 +104,7 @@ internal class QuestionnaireEditAdapter(
QuestionnaireViewHolderType.GROUP -> GroupViewHolderFactory
QuestionnaireViewHolderType.BOOLEAN_TYPE_PICKER -> BooleanChoiceViewHolderFactory
QuestionnaireViewHolderType.DATE_PICKER -> DatePickerViewHolderFactory
QuestionnaireViewHolderType.TIME_PICKER -> TimePickerViewHolderFactory
QuestionnaireViewHolderType.DATE_TIME_PICKER -> DateTimePickerViewHolderFactory
QuestionnaireViewHolderType.EDIT_TEXT_SINGLE_LINE -> EditTextSingleLineViewHolderFactory
QuestionnaireViewHolderType.EDIT_TEXT_MULTI_LINE -> EditTextMultiLineViewHolderFactory
Expand Down Expand Up @@ -223,6 +225,7 @@ internal class QuestionnaireEditAdapter(
QuestionnaireItemType.GROUP -> QuestionnaireViewHolderType.GROUP
QuestionnaireItemType.BOOLEAN -> QuestionnaireViewHolderType.BOOLEAN_TYPE_PICKER
QuestionnaireItemType.DATE -> QuestionnaireViewHolderType.DATE_PICKER
QuestionnaireItemType.TIME -> QuestionnaireViewHolderType.TIME_PICKER
QuestionnaireItemType.DATETIME -> QuestionnaireViewHolderType.DATE_TIME_PICKER
QuestionnaireItemType.STRING -> getStringViewHolderType(questionnaireViewItem)
QuestionnaireItemType.TEXT -> QuestionnaireViewHolderType.EDIT_TEXT_MULTI_LINE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2023-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,6 +45,7 @@ enum class QuestionnaireViewHolderType(val value: Int) {
SLIDER(15),
PHONE_NUMBER(16),
ATTACHMENT(17),
TIME_PICKER(18),
;

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.google.android.fhir.datacapture.extensions.isHelpCode
import com.google.android.fhir.datacapture.extensions.isHidden
import com.google.android.fhir.datacapture.extensions.isPaginated
import com.google.android.fhir.datacapture.extensions.isRepeatedGroup
import com.google.android.fhir.datacapture.extensions.launchTimestamp
import com.google.android.fhir.datacapture.extensions.localizedTextSpanned
import com.google.android.fhir.datacapture.extensions.maxValue
import com.google.android.fhir.datacapture.extensions.maxValueCqfCalculatedValueExpression
Expand All @@ -64,6 +65,7 @@ import com.google.android.fhir.datacapture.validation.Valid
import com.google.android.fhir.datacapture.validation.ValidationResult
import com.google.android.fhir.datacapture.views.QuestionTextConfiguration
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import java.util.Date
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -74,6 +76,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand Down Expand Up @@ -160,6 +163,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
.forEach { questionnaireResponse.addItem(it.createQuestionnaireResponseItem()) }
}
}
// Add extension for questionnaire launch time stamp
questionnaireResponse.launchTimestamp = DateTimeType(Date())
questionnaireResponse.packRepeatedGroups(questionnaire)
}

Expand Down Expand Up @@ -475,6 +480,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
)
.map { it.copy() }
unpackRepeatedGroups(this@QuestionnaireViewModel.questionnaire)
// Use authored as a submission time stamp
authored = Date()
}
}

Expand Down Expand Up @@ -1176,7 +1183,7 @@ internal data class QuestionnairePage(
)

internal val QuestionnairePagination.hasPreviousPage: Boolean
get() = pages.any { it.index < currentPageIndex && it.enabled }
get() = pages.any { it.index < currentPageIndex && it.enabled && !it.hidden }

internal val QuestionnairePagination.hasNextPage: Boolean
get() = pages.any { it.index > currentPageIndex && it.enabled }
get() = pages.any { it.index > currentPageIndex && it.enabled && !it.hidden }
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.extensions
import android.content.Context
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Button
Expand Down Expand Up @@ -82,7 +83,10 @@ fun initHelpViews(
}
}
}
helpTextView.updateTextAndVisibility(questionnaireItem.localizedHelpSpanned)
helpTextView.apply {
updateTextAndVisibility(questionnaireItem.localizedHelpSpanned)
movementMethod = LinkMovementMethod.getInstance()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package com.google.android.fhir.datacapture.extensions

import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

internal const val EXTENSION_LAST_LAUNCHED_TIMESTAMP: String =
"http://github.com/google-android/questionnaire-lastLaunched-timestamp"

/** Pre-order list of all questionnaire response items in the questionnaire. */
val QuestionnaireResponse.allItems: List<QuestionnaireResponse.QuestionnaireResponseItemComponent>
get() = item.flatMap { it.descendant }
Expand Down Expand Up @@ -146,3 +151,20 @@ private fun unpackRepeatedGroups(
listOf(questionnaireResponseItem)
}
}

/**
* Adds a launch timestamp extension to the Questionnaire Response. If the extension @see
* EXTENSION_LAUNCH_TIMESTAMP already exists, it updates its value; otherwise, it adds a new one.
*/
internal var QuestionnaireResponse.launchTimestamp: DateTimeType?
get() {
val extension = this.extension.firstOrNull { it.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }
return extension?.value as? DateTimeType
}
set(value) {
extension.find { it.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }?.setValue(value)
?: run {
// Add a new extension if none exists
extension.add(Extension(EXTENSION_LAST_LAUNCHED_TIMESTAMP, value))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 Google LLC
* Copyright 2022-2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -151,10 +151,12 @@ object QuestionnaireResponseValidator {
questionnaireResponseItemValidator: QuestionnaireResponseItemValidator,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>,
): Map<String, List<ValidationResult>> {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Questionnaire.QuestionnaireItemType.DISPLAY,
Questionnaire.QuestionnaireItemType.NULL, -> Unit
Questionnaire.QuestionnaireItemType.GROUP ->
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }
when {
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
!questionnaireItem.repeats ->
// Nested items under group
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
validateQuestionnaireResponseItems(
Expand Down Expand Up @@ -262,10 +264,13 @@ object QuestionnaireResponseValidator {
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
) {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Questionnaire.QuestionnaireItemType.DISPLAY,
Questionnaire.QuestionnaireItemType.NULL, -> Unit
Questionnaire.QuestionnaireItemType.GROUP ->
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }

when {
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
!questionnaireItem.repeats ->
// Nested items under group
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
checkQuestionnaireResponseItems(questionnaireItem.item, questionnaireResponseItem.item)
Expand Down
Loading

0 comments on commit c911cb2

Please sign in to comment.