Skip to content

Commit 0d752c7

Browse files
committed
Merge branch 'master' into master-release
2 parents a04d47f + 2a7e91f commit 0d752c7

File tree

38 files changed

+2217
-593
lines changed

38 files changed

+2217
-593
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"resourceType": "Questionnaire",
3+
"item": [
4+
{
5+
"linkId": "1",
6+
"text": "Enter a time",
7+
"type": "time",
8+
"extension": [
9+
{
10+
"url": "http://hl7.org/fhir/StructureDefinition/entryFormat",
11+
"valueString": "hh-mm"
12+
}
13+
],
14+
"item": [
15+
{
16+
"extension": [
17+
{
18+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
19+
"valueCodeableConcept": {
20+
"coding": [
21+
{
22+
"system": "http://hl7.org/fhir/questionnaire-display-category",
23+
"code": "instructions"
24+
}
25+
]
26+
}
27+
}
28+
],
29+
"linkId": "1-most-recent",
30+
"text": "Use keyboard entry or time picker",
31+
"type": "display"
32+
}
33+
]
34+
}
35+
]
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"resourceType": "Questionnaire",
3+
"item": [
4+
{
5+
"linkId": "1",
6+
"text": "Enter a time",
7+
"type": "time",
8+
"required": true,
9+
"extension": [
10+
{
11+
"url": "http://hl7.org/fhir/StructureDefinition/entryFormat",
12+
"valueString": "hh-mm"
13+
}
14+
],
15+
"item": [
16+
{
17+
"extension": [
18+
{
19+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
20+
"valueCodeableConcept": {
21+
"coding": [
22+
{
23+
"system": "http://hl7.org/fhir/questionnaire-display-category",
24+
"code": "instructions"
25+
}
26+
]
27+
}
28+
}
29+
],
30+
"linkId": "1-most-recent",
31+
"text": "Use keyboard entry or time picker",
32+
"type": "display"
33+
}
34+
]
35+
}
36+
]
37+
}

catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt

+7
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ class ComponentListViewModel(application: Application, private val state: SavedS
102102
"component_date_picker.json",
103103
"component_date_picker_with_validation.json",
104104
),
105+
TIME_PICKER(
106+
R.drawable.ic_timepicker,
107+
R.string.component_name_time_picker,
108+
"component_time_picker.json",
109+
"component_time_picker_with_validation.json",
110+
),
105111
DATE_TIME_PICKER(
106112
R.drawable.ic_timepicker,
107113
R.string.component_name_date_time_picker,
@@ -171,6 +177,7 @@ class ComponentListViewModel(application: Application, private val state: SavedS
171177
ViewItem.ComponentItem(Component.TEXT_FIELD),
172178
ViewItem.ComponentItem(Component.AUTO_COMPLETE),
173179
ViewItem.ComponentItem(Component.DATE_PICKER),
180+
ViewItem.ComponentItem(Component.TIME_PICKER),
174181
ViewItem.ComponentItem(Component.DATE_TIME_PICKER),
175182
ViewItem.ComponentItem(Component.SLIDER),
176183
ViewItem.ComponentItem(Component.QUANTITY),

catalog/src/main/res/values/strings.xml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<string name="component_name_text_field">Text field </string>
2929
<string name="component_name_auto_complete">Auto Complete</string>
3030
<string name="component_name_date_picker">Date picker</string>
31+
<string name="component_name_time_picker">Time picker</string>
3132
<string name="component_name_date_time_picker">DateTime picker</string>
3233
<string name="component_name_slider">Slider</string>
3334
<string name="component_name_quantity">Quantity</string>

datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireEditAdapter.kt

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemView
4747
import com.google.android.fhir.datacapture.views.factories.RadioGroupViewHolderFactory
4848
import com.google.android.fhir.datacapture.views.factories.RepeatedGroupHeaderItemViewHolder
4949
import com.google.android.fhir.datacapture.views.factories.SliderViewHolderFactory
50+
import com.google.android.fhir.datacapture.views.factories.TimePickerViewHolderFactory
5051
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType
5152

5253
internal class QuestionnaireEditAdapter(
@@ -103,6 +104,7 @@ internal class QuestionnaireEditAdapter(
103104
QuestionnaireViewHolderType.GROUP -> GroupViewHolderFactory
104105
QuestionnaireViewHolderType.BOOLEAN_TYPE_PICKER -> BooleanChoiceViewHolderFactory
105106
QuestionnaireViewHolderType.DATE_PICKER -> DatePickerViewHolderFactory
107+
QuestionnaireViewHolderType.TIME_PICKER -> TimePickerViewHolderFactory
106108
QuestionnaireViewHolderType.DATE_TIME_PICKER -> DateTimePickerViewHolderFactory
107109
QuestionnaireViewHolderType.EDIT_TEXT_SINGLE_LINE -> EditTextSingleLineViewHolderFactory
108110
QuestionnaireViewHolderType.EDIT_TEXT_MULTI_LINE -> EditTextMultiLineViewHolderFactory
@@ -223,6 +225,7 @@ internal class QuestionnaireEditAdapter(
223225
QuestionnaireItemType.GROUP -> QuestionnaireViewHolderType.GROUP
224226
QuestionnaireItemType.BOOLEAN -> QuestionnaireViewHolderType.BOOLEAN_TYPE_PICKER
225227
QuestionnaireItemType.DATE -> QuestionnaireViewHolderType.DATE_PICKER
228+
QuestionnaireItemType.TIME -> QuestionnaireViewHolderType.TIME_PICKER
226229
QuestionnaireItemType.DATETIME -> QuestionnaireViewHolderType.DATE_TIME_PICKER
227230
QuestionnaireItemType.STRING -> getStringViewHolderType(questionnaireViewItem)
228231
QuestionnaireItemType.TEXT -> QuestionnaireViewHolderType.EDIT_TEXT_MULTI_LINE

datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewHolderType.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Google LLC
2+
* Copyright 2023-2024 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ enum class QuestionnaireViewHolderType(val value: Int) {
4545
SLIDER(15),
4646
PHONE_NUMBER(16),
4747
ATTACHMENT(17),
48+
TIME_PICKER(18),
4849
;
4950

5051
companion object {

datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ internal data class QuestionnairePage(
11831183
)
11841184

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

11881188
internal val QuestionnairePagination.hasNextPage: Boolean
1189-
get() = pages.any { it.index > currentPageIndex && it.enabled }
1189+
get() = pages.any { it.index > currentPageIndex && it.enabled && !it.hidden }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.fhir.datacapture.views.factories
18+
19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.text.InputType
22+
import android.text.format.DateFormat
23+
import android.view.View
24+
import androidx.appcompat.app.AppCompatActivity
25+
import androidx.lifecycle.lifecycleScope
26+
import com.google.android.fhir.datacapture.R
27+
import com.google.android.fhir.datacapture.extensions.getRequiredOrOptionalText
28+
import com.google.android.fhir.datacapture.extensions.toLocalizedString
29+
import com.google.android.fhir.datacapture.extensions.tryUnwrapContext
30+
import com.google.android.fhir.datacapture.views.HeaderView
31+
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
32+
import com.google.android.material.textfield.TextInputEditText
33+
import com.google.android.material.textfield.TextInputLayout
34+
import com.google.android.material.timepicker.MaterialTimePicker
35+
import com.google.android.material.timepicker.MaterialTimePicker.INPUT_MODE_CLOCK
36+
import com.google.android.material.timepicker.MaterialTimePicker.INPUT_MODE_KEYBOARD
37+
import com.google.android.material.timepicker.TimeFormat
38+
import java.time.LocalTime
39+
import java.time.format.DateTimeFormatter
40+
import kotlinx.coroutines.launch
41+
import org.hl7.fhir.r4.model.QuestionnaireResponse
42+
import org.hl7.fhir.r4.model.TimeType
43+
44+
object TimePickerViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.time_picker_view) {
45+
46+
override fun getQuestionnaireItemViewHolderDelegate() =
47+
object : QuestionnaireItemViewHolderDelegate {
48+
private val TAG = "time-picker"
49+
private lateinit var context: AppCompatActivity
50+
private lateinit var header: HeaderView
51+
private lateinit var timeInputLayout: TextInputLayout
52+
private lateinit var timeInputEditText: TextInputEditText
53+
override lateinit var questionnaireViewItem: QuestionnaireViewItem
54+
55+
override fun init(itemView: View) {
56+
context = itemView.context.tryUnwrapContext()!!
57+
header = itemView.findViewById(R.id.header)
58+
timeInputLayout = itemView.findViewById(R.id.text_input_layout)
59+
timeInputEditText = itemView.findViewById(R.id.text_input_edit_text)
60+
timeInputEditText.inputType = InputType.TYPE_NULL
61+
timeInputEditText.hint = itemView.context.getString(R.string.time)
62+
63+
timeInputLayout.setEndIconOnClickListener {
64+
// The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment
65+
// and again in TextInputEditText during layout inflation. As a result, it is
66+
// necessary to access the base context twice to retrieve the application object
67+
// from the view's context.
68+
val context = itemView.context.tryUnwrapContext()!!
69+
buildMaterialTimePicker(context, INPUT_MODE_CLOCK)
70+
}
71+
timeInputEditText.setOnClickListener {
72+
buildMaterialTimePicker(itemView.context, INPUT_MODE_KEYBOARD)
73+
}
74+
}
75+
76+
@SuppressLint("NewApi") // java.time APIs can be used due to desugaring
77+
override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
78+
clearPreviousState()
79+
header.bind(questionnaireViewItem)
80+
timeInputLayout.helperText = getRequiredOrOptionalText(questionnaireViewItem, context)
81+
82+
val questionnaireItemViewItemDateTimeAnswer =
83+
questionnaireViewItem.answers.singleOrNull()?.valueTimeType?.localTime
84+
85+
// If there is no set answer in the QuestionnaireItemViewItem, make the time field empty.
86+
timeInputEditText.setText(
87+
questionnaireItemViewItemDateTimeAnswer?.toLocalizedString(timeInputEditText.context)
88+
?: "",
89+
)
90+
}
91+
92+
override fun setReadOnly(isReadOnly: Boolean) {
93+
// The system outside this delegate should only be able to mark it read only. Otherwise, it
94+
// will change the state set by this delegate in bindView().
95+
if (isReadOnly) {
96+
timeInputEditText.isEnabled = false
97+
timeInputLayout.isEnabled = false
98+
}
99+
}
100+
101+
private fun buildMaterialTimePicker(context: Context, inputMode: Int) {
102+
val selectedTime =
103+
questionnaireViewItem.answers.singleOrNull()?.valueTimeType?.localTime ?: LocalTime.now()
104+
val timeFormat =
105+
if (DateFormat.is24HourFormat(context)) {
106+
TimeFormat.CLOCK_24H
107+
} else {
108+
TimeFormat.CLOCK_12H
109+
}
110+
MaterialTimePicker.Builder()
111+
.setTitleText(R.string.select_time)
112+
.setHour(selectedTime.hour)
113+
.setMinute(selectedTime.minute)
114+
.setTimeFormat(timeFormat)
115+
.setInputMode(inputMode)
116+
.build()
117+
.apply {
118+
addOnPositiveButtonClickListener {
119+
with(LocalTime.of(this.hour, this.minute, 0)) {
120+
timeInputEditText.setText(this.toLocalizedString(context))
121+
setQuestionnaireItemViewItemAnswer(this)
122+
timeInputEditText.clearFocus()
123+
}
124+
}
125+
}
126+
.show(context.tryUnwrapContext()!!.supportFragmentManager, TAG)
127+
}
128+
129+
/** Set the answer in the [QuestionnaireResponse]. */
130+
private fun setQuestionnaireItemViewItemAnswer(localDateTime: LocalTime) =
131+
context.lifecycleScope.launch {
132+
questionnaireViewItem.setAnswer(
133+
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
134+
.setValue(TimeType(localDateTime.format(DateTimeFormatter.ISO_TIME))),
135+
)
136+
}
137+
138+
private fun clearPreviousState() {
139+
timeInputEditText.isEnabled = true
140+
timeInputLayout.isEnabled = true
141+
}
142+
}
143+
144+
private val TimeType.localTime
145+
get() =
146+
LocalTime.of(
147+
hour,
148+
minute,
149+
second.toInt(),
150+
)
151+
}

datacapture/src/main/res/layout/edit_text_single_line_view.xml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<com.google.android.material.textfield.TextInputEditText
5353
android:id="@+id/text_input_edit_text"
5454
style="?attr/questionnaireTextInputEditTextStyle"
55+
android:inputType="text"
5556
android:layout_width="match_parent"
5657
android:layout_height="wrap_content"
5758
android:maxLines="1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<!--
3+
Copyright 2020 Google LLC
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<LinearLayout
15+
xmlns:android="http://schemas.android.com/apk/res/android"
16+
android:layout_width="match_parent"
17+
android:layout_height="wrap_content"
18+
android:layout_marginHorizontal="@dimen/item_margin_horizontal"
19+
android:layout_marginVertical="@dimen/item_margin_vertical"
20+
android:orientation="vertical"
21+
>
22+
23+
<com.google.android.fhir.datacapture.views.HeaderView
24+
android:id="@+id/header"
25+
android:layout_width="match_parent"
26+
android:layout_height="wrap_content"
27+
/>
28+
29+
<com.google.android.fhir.datacapture.views.MediaView
30+
android:id="@+id/item_media"
31+
android:layout_width="match_parent"
32+
android:layout_height="wrap_content"
33+
/>
34+
35+
<com.google.android.material.textfield.TextInputLayout
36+
xmlns:app="http://schemas.android.com/apk/res-auto"
37+
android:id="@+id/text_input_layout"
38+
style="?attr/questionnaireTextInputLayoutStyle"
39+
android:layout_width="match_parent"
40+
android:layout_height="wrap_content"
41+
app:endIconDrawable="@drawable/gm_schedule_24"
42+
app:endIconMode="custom"
43+
app:errorIconDrawable="@null"
44+
app:hintEnabled="true"
45+
>
46+
47+
<com.google.android.material.textfield.TextInputEditText
48+
android:id="@+id/text_input_edit_text"
49+
style="?attr/questionnaireTextInputEditTextStyle"
50+
android:layout_width="match_parent"
51+
android:layout_height="wrap_content"
52+
android:inputType="time"
53+
android:singleLine="true"
54+
/>
55+
56+
</com.google.android.material.textfield.TextInputLayout>
57+
</LinearLayout>

datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewHolderTypeTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Google LLC
2+
* Copyright 2022-2024 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.robolectric.annotation.Config
2828
class QuestionnaireViewHolderTypeTest {
2929
@Test
3030
fun size_shouldReturnNumberOfQuestionnaireViewHolderTypes() {
31-
assertThat(QuestionnaireViewHolderType.values().size).isEqualTo(18)
31+
assertThat(QuestionnaireViewHolderType.values().size).isEqualTo(19)
3232
}
3333

3434
@Test

0 commit comments

Comments
 (0)