Skip to content

Commit 62dce6f

Browse files
committed
Migrate QuestionnaireItemDialogSelectViewHolderFactory to compose
1 parent 39ecb2c commit 62dce6f

File tree

10 files changed

+1362
-40
lines changed

10 files changed

+1362
-40
lines changed

datacapture/src/androidTest/java/com/google/android/fhir/datacapture/test/views/QuestionnaireItemDialogMultiSelectViewHolderFactoryEspressoTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 Google LLC
2+
* Copyright 2023-2025 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.
@@ -47,7 +47,7 @@ import com.google.android.fhir.datacapture.validation.Invalid
4747
import com.google.android.fhir.datacapture.validation.NotValidated
4848
import com.google.android.fhir.datacapture.views.QuestionTextConfiguration
4949
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
50-
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemDialogSelectViewHolderFactory
50+
import com.google.android.fhir.datacapture.views.factories.DialogSelectViewHolderFactory
5151
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
5252
import com.google.android.material.textfield.TextInputLayout
5353
import com.google.common.truth.StringSubject
@@ -76,7 +76,7 @@ class QuestionnaireItemDialogMultiSelectViewHolderFactoryEspressoTest {
7676
@Before
7777
fun setup() {
7878
activityScenarioRule.scenario.onActivity { activity -> parent = FrameLayout(activity) }
79-
viewHolder = QuestionnaireItemDialogSelectViewHolderFactory.create(parent)
79+
viewHolder = DialogSelectViewHolderFactory.create(parent)
8080
setTestLayout(viewHolder.itemView)
8181
}
8282

datacapture/src/androidTest/java/com/google/android/fhir/datacapture/test/views/QuestionnaireItemMultiSelectHolderFactoryInstrumentedTest.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Google LLC
2+
* Copyright 2023-2025 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.
@@ -26,7 +26,7 @@ import com.google.android.fhir.datacapture.test.TestActivity
2626
import com.google.android.fhir.datacapture.validation.Invalid
2727
import com.google.android.fhir.datacapture.validation.NotValidated
2828
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
29-
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemDialogSelectViewHolderFactory
29+
import com.google.android.fhir.datacapture.views.factories.DialogSelectViewHolderFactory
3030
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
3131
import com.google.android.material.textfield.TextInputLayout
3232
import com.google.common.truth.Truth.assertThat
@@ -145,9 +145,7 @@ class QuestionnaireItemMultiSelectHolderFactoryInstrumentedTest {
145145
private inline fun withViewHolder(
146146
crossinline block: TestActivity.(QuestionnaireItemViewHolder) -> Unit,
147147
) {
148-
rule.scenario.onActivity {
149-
block(it, QuestionnaireItemDialogSelectViewHolderFactory.create(FrameLayout(it)))
150-
}
148+
rule.scenario.onActivity { block(it, DialogSelectViewHolderFactory.create(FrameLayout(it))) }
151149
}
152150
}
153151

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import com.google.android.fhir.datacapture.views.factories.BooleanChoiceViewHold
4343
import com.google.android.fhir.datacapture.views.factories.CheckBoxGroupViewHolderFactory
4444
import com.google.android.fhir.datacapture.views.factories.DatePickerViewHolderFactory
4545
import com.google.android.fhir.datacapture.views.factories.DateTimePickerViewHolderFactory
46+
import com.google.android.fhir.datacapture.views.factories.DialogSelectViewHolderFactory
4647
import com.google.android.fhir.datacapture.views.factories.DisplayViewHolderFactory
4748
import com.google.android.fhir.datacapture.views.factories.DropDownViewHolderFactory
4849
import com.google.android.fhir.datacapture.views.factories.EditTextDecimalViewHolderFactory
@@ -51,7 +52,6 @@ import com.google.android.fhir.datacapture.views.factories.EditTextMultiLineView
5152
import com.google.android.fhir.datacapture.views.factories.EditTextSingleLineViewHolderFactory
5253
import com.google.android.fhir.datacapture.views.factories.GroupViewHolderFactory
5354
import com.google.android.fhir.datacapture.views.factories.QuantityViewHolderFactory
54-
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemDialogSelectViewHolderFactory
5555
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
5656
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderFactory
5757
import com.google.android.fhir.datacapture.views.factories.RadioGroupViewHolderFactory
@@ -282,7 +282,7 @@ private fun getQuestionnaireItemViewHolderFactory(
282282
QuestionnaireViewHolderType.QUANTITY -> QuantityViewHolderFactory
283283
QuestionnaireViewHolderType.CHECK_BOX_GROUP -> CheckBoxGroupViewHolderFactory
284284
QuestionnaireViewHolderType.AUTO_COMPLETE -> AutoCompleteViewHolderFactory
285-
QuestionnaireViewHolderType.DIALOG_SELECT -> QuestionnaireItemDialogSelectViewHolderFactory
285+
QuestionnaireViewHolderType.DIALOG_SELECT -> DialogSelectViewHolderFactory
286286
QuestionnaireViewHolderType.SLIDER -> SliderViewHolderFactory
287287
QuestionnaireViewHolderType.PHONE_NUMBER -> PhoneNumberViewHolderFactory
288288
QuestionnaireViewHolderType.ATTACHMENT -> AttachmentViewHolderFactory
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2025 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.compose
18+
19+
import android.graphics.drawable.Drawable
20+
import androidx.compose.foundation.background
21+
import androidx.compose.foundation.border
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.padding
25+
import androidx.compose.foundation.layout.size
26+
import androidx.compose.foundation.layout.width
27+
import androidx.compose.foundation.selection.toggleable
28+
import androidx.compose.foundation.shape.RoundedCornerShape
29+
import androidx.compose.material3.Checkbox
30+
import androidx.compose.material3.CheckboxDefaults
31+
import androidx.compose.material3.Icon
32+
import androidx.compose.material3.MaterialTheme
33+
import androidx.compose.material3.Text
34+
import androidx.compose.runtime.Composable
35+
import androidx.compose.ui.Alignment
36+
import androidx.compose.ui.Modifier
37+
import androidx.compose.ui.draw.clip
38+
import androidx.compose.ui.graphics.asImageBitmap
39+
import androidx.compose.ui.platform.testTag
40+
import androidx.compose.ui.res.dimensionResource
41+
import androidx.compose.ui.semantics.Role
42+
import androidx.compose.ui.text.AnnotatedString
43+
import androidx.compose.ui.unit.dp
44+
import androidx.core.graphics.drawable.toBitmap
45+
import com.google.android.fhir.datacapture.R
46+
47+
@Composable
48+
internal fun ChoiceCheckbox(
49+
label: AnnotatedString,
50+
checked: Boolean,
51+
enabled: Boolean,
52+
modifier: Modifier = Modifier,
53+
image: Drawable? = null,
54+
onCheckedChange: (Boolean) -> Unit,
55+
) {
56+
val backgroundColor =
57+
if (checked) {
58+
MaterialTheme.colorScheme.primary.copy(alpha = 0.12f)
59+
} else {
60+
MaterialTheme.colorScheme.surface
61+
}
62+
63+
val borderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f)
64+
val textColor =
65+
if (checked) {
66+
MaterialTheme.colorScheme.primary
67+
} else {
68+
MaterialTheme.colorScheme.onSurface
69+
}
70+
71+
val shape =
72+
if (checked) {
73+
RoundedCornerShape(4.dp)
74+
} else {
75+
RoundedCornerShape(8.dp)
76+
}
77+
78+
Row(
79+
modifier =
80+
modifier
81+
.clip(shape)
82+
.background(backgroundColor)
83+
.then(
84+
if (!checked) {
85+
Modifier.border(1.dp, borderColor, shape)
86+
} else {
87+
Modifier
88+
},
89+
)
90+
.toggleable(
91+
value = checked,
92+
enabled = enabled,
93+
role = Role.Checkbox,
94+
onValueChange = onCheckedChange,
95+
)
96+
.padding(
97+
start = dimensionResource(R.dimen.option_item_between_text_and_icon_padding),
98+
end = dimensionResource(R.dimen.option_item_after_text_padding),
99+
top = dimensionResource(R.dimen.option_item_padding),
100+
bottom = dimensionResource(R.dimen.option_item_padding),
101+
),
102+
verticalAlignment = Alignment.CenterVertically,
103+
) {
104+
Checkbox(
105+
checked = checked,
106+
onCheckedChange = null,
107+
enabled = enabled,
108+
colors =
109+
CheckboxDefaults.colors(
110+
checkedColor = MaterialTheme.colorScheme.primary,
111+
uncheckedColor = MaterialTheme.colorScheme.onSurface,
112+
checkmarkColor = MaterialTheme.colorScheme.surface,
113+
),
114+
)
115+
// Display image
116+
image?.let { drawable ->
117+
Spacer(modifier = Modifier.width(8.dp))
118+
Icon(
119+
bitmap = drawable.toBitmap().asImageBitmap(),
120+
contentDescription = null,
121+
modifier = Modifier.testTag(CHOICE_CHECKBOX_IMAGE_TAG).size(24.dp),
122+
)
123+
}
124+
Spacer(
125+
modifier =
126+
Modifier.width(dimensionResource(R.dimen.option_item_between_text_and_icon_padding)),
127+
)
128+
Text(
129+
text = label,
130+
color = textColor,
131+
)
132+
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.option_item_after_text_padding)))
133+
}
134+
}
135+
136+
const val CHOICE_CHECKBOX_IMAGE_TAG = "checkbox_option_icon"
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2025 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.compose
18+
19+
import android.graphics.drawable.Drawable
20+
import androidx.compose.foundation.background
21+
import androidx.compose.foundation.border
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.padding
25+
import androidx.compose.foundation.layout.size
26+
import androidx.compose.foundation.layout.width
27+
import androidx.compose.foundation.selection.selectable
28+
import androidx.compose.foundation.shape.RoundedCornerShape
29+
import androidx.compose.material3.Icon
30+
import androidx.compose.material3.MaterialTheme
31+
import androidx.compose.material3.RadioButton
32+
import androidx.compose.material3.Text
33+
import androidx.compose.runtime.Composable
34+
import androidx.compose.ui.Alignment
35+
import androidx.compose.ui.Modifier
36+
import androidx.compose.ui.draw.clip
37+
import androidx.compose.ui.graphics.asImageBitmap
38+
import androidx.compose.ui.platform.testTag
39+
import androidx.compose.ui.res.dimensionResource
40+
import androidx.compose.ui.semantics.Role
41+
import androidx.compose.ui.text.AnnotatedString
42+
import androidx.compose.ui.unit.dp
43+
import androidx.core.graphics.drawable.toBitmap
44+
import com.google.android.fhir.datacapture.R
45+
46+
@Composable
47+
internal fun ChoiceRadioButton(
48+
label: AnnotatedString,
49+
selected: Boolean,
50+
enabled: Boolean,
51+
modifier: Modifier = Modifier,
52+
image: Drawable? = null,
53+
onClick: () -> Unit,
54+
) {
55+
val backgroundColor =
56+
if (selected) {
57+
MaterialTheme.colorScheme.primary.copy(alpha = 0.12f)
58+
} else {
59+
MaterialTheme.colorScheme.surface
60+
}
61+
62+
val borderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f)
63+
val textColor =
64+
if (selected) {
65+
MaterialTheme.colorScheme.primary
66+
} else {
67+
MaterialTheme.colorScheme.onSurface
68+
}
69+
70+
val shape =
71+
if (selected) {
72+
RoundedCornerShape(4.dp)
73+
} else {
74+
RoundedCornerShape(8.dp)
75+
}
76+
77+
Row(
78+
modifier =
79+
modifier
80+
.clip(shape)
81+
.background(backgroundColor)
82+
.then(
83+
if (!selected) {
84+
Modifier.border(1.dp, borderColor, shape)
85+
} else {
86+
Modifier
87+
},
88+
)
89+
.selectable(
90+
selected = selected,
91+
enabled = enabled,
92+
role = Role.RadioButton,
93+
onClick = onClick,
94+
)
95+
.padding(
96+
start = dimensionResource(R.dimen.option_item_between_text_and_icon_padding),
97+
end = dimensionResource(R.dimen.option_item_after_text_padding),
98+
top = dimensionResource(R.dimen.option_item_padding),
99+
bottom = dimensionResource(R.dimen.option_item_padding),
100+
),
101+
verticalAlignment = Alignment.CenterVertically,
102+
) {
103+
RadioButton(
104+
selected = selected,
105+
onClick = null,
106+
enabled = enabled,
107+
)
108+
// Display image
109+
image?.let { drawable ->
110+
Spacer(modifier = Modifier.width(8.dp))
111+
Icon(
112+
bitmap = drawable.toBitmap().asImageBitmap(),
113+
contentDescription = null,
114+
modifier = Modifier.testTag(CHOICE_RADIO_BUTTON_IMAGE_TAG).size(24.dp),
115+
)
116+
}
117+
Spacer(
118+
modifier =
119+
Modifier.width(dimensionResource(R.dimen.option_item_between_text_and_icon_padding)),
120+
)
121+
Text(
122+
text = label,
123+
color = textColor,
124+
)
125+
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.option_item_after_text_padding)))
126+
}
127+
}
128+
129+
const val CHOICE_RADIO_BUTTON_IMAGE_TAG = "radio_button_option_icon"

0 commit comments

Comments
 (0)