@@ -49,7 +49,7 @@ import com.google.android.fhir.datacapture.views.factories.DropDownAnswerOption
4949
5050@OptIn(ExperimentalMaterial3Api ::class )
5151@Composable
52- internal fun ExposedDropDownMenuBoxItem (
52+ internal fun ReadOnlyExposedDropDownMenuBoxItem (
5353 modifier : Modifier ,
5454 enabled : Boolean ,
5555 labelText : AnnotatedString ? = null,
@@ -106,6 +106,7 @@ internal fun ExposedDropDownMenuBoxItem(
106106 if (showClearIcon) {
107107 IconButton (
108108 onClick = { selectedDropDownAnswerOption = null },
109+ enabled = enabled,
109110 modifier = Modifier .testTag(CLEAR_TEXT_ICON_BUTTON_TAG ),
110111 ) {
111112 Icon (painterResource(R .drawable.ic_clear), contentDescription = " clear" )
@@ -143,6 +144,208 @@ internal fun ExposedDropDownMenuBoxItem(
143144 }
144145}
145146
147+ @OptIn(ExperimentalMaterial3Api ::class )
148+ @Composable
149+ internal fun FilterableExposedDropDownMenuBoxItem (
150+ modifier : Modifier ,
151+ enabled : Boolean ,
152+ labelText : AnnotatedString ? = null,
153+ supportingText : String? = null,
154+ isError : Boolean = false,
155+ showClearIcon : Boolean = false,
156+ readOnly : Boolean = showClearIcon,
157+ selectedOption : DropDownAnswerOption ? = null,
158+ options : List <DropDownAnswerOption >,
159+ onDropDownAnswerOptionSelected : (DropDownAnswerOption ? ) -> Unit ,
160+ ) {
161+ var expanded by remember { mutableStateOf(false ) }
162+ var selectedDropDownAnswerOption by
163+ remember(selectedOption, options) { mutableStateOf(selectedOption) }
164+ var selectedOptionDisplay by
165+ remember(selectedDropDownAnswerOption) {
166+ mutableStateOf(selectedDropDownAnswerOption?.answerOptionString ? : " " )
167+ }
168+
169+ LaunchedEffect (selectedDropDownAnswerOption) {
170+ onDropDownAnswerOptionSelected(selectedDropDownAnswerOption)
171+ }
172+
173+ ExposedDropdownMenuBox (
174+ modifier = modifier,
175+ expanded = expanded,
176+ onExpandedChange = { expanded = it },
177+ ) {
178+ OutlinedTextField (
179+ value = selectedOptionDisplay,
180+ onValueChange = { selectedOptionDisplay = it },
181+ modifier =
182+ Modifier .fillMaxWidth()
183+ .testTag(DROP_DOWN_TEXT_FIELD_TAG )
184+ .semantics { if (isError) error(supportingText ? : " " ) }
185+ .menuAnchor(MenuAnchorType .PrimaryEditable , enabled),
186+ readOnly = readOnly,
187+ enabled = enabled,
188+ minLines = 1 ,
189+ isError = isError,
190+ label = { labelText?.let { Text (it) } },
191+ supportingText = { supportingText?.let { Text (it) } },
192+ leadingIcon =
193+ selectedDropDownAnswerOption?.answerOptionImage?.let {
194+ {
195+ Icon (
196+ it.toBitmap().asImageBitmap(),
197+ contentDescription = selectedOptionDisplay,
198+ modifier = Modifier .testTag(DROP_DOWN_TEXT_FIELD_LEADING_ICON_TAG ),
199+ )
200+ }
201+ },
202+ trailingIcon = {
203+ Row (verticalAlignment = Alignment .CenterVertically ) {
204+ if (showClearIcon) {
205+ IconButton (
206+ onClick = { selectedDropDownAnswerOption = null },
207+ modifier = Modifier .testTag(CLEAR_TEXT_ICON_BUTTON_TAG ),
208+ ) {
209+ Icon (painterResource(R .drawable.ic_clear), contentDescription = " clear" )
210+ }
211+ }
212+ ExposedDropdownMenuDefaults .TrailingIcon (expanded = expanded, modifier = Modifier .menuAnchor(MenuAnchorType .SecondaryEditable , enabled))
213+ }
214+ },
215+ )
216+
217+ val filteredOptions = options.filter { it.answerOptionString.contains(selectedOptionDisplay, true ) }
218+ if (filteredOptions.isNotEmpty()) {
219+ ExposedDropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
220+ filteredOptions.forEach { option ->
221+ DropdownMenuItem (
222+ modifier = Modifier .testTag(DROP_DOWN_MENU_ITEM_TAG ),
223+ text = {
224+ Text (option.answerOptionAnnotatedString(), style = MaterialTheme .typography.bodyLarge)
225+ },
226+ leadingIcon =
227+ option.answerOptionImage?.let {
228+ {
229+ Icon (
230+ it.toBitmap().asImageBitmap(),
231+ contentDescription = option.answerOptionString,
232+ )
233+ }
234+ },
235+ enabled = enabled,
236+ onClick = {
237+ selectedDropDownAnswerOption = option
238+ expanded = false
239+ },
240+ contentPadding = ExposedDropdownMenuDefaults .ItemContentPadding ,
241+ )
242+ }
243+ }
244+ }
245+ }
246+ }
247+
248+ @OptIn(ExperimentalMaterial3Api ::class )
249+ @Composable
250+ internal fun AutoCompleteExposedDropDownMenuBoxItem (
251+ modifier : Modifier ,
252+ enabled : Boolean ,
253+ labelText : AnnotatedString ? = null,
254+ supportingText : String? = null,
255+ isError : Boolean = false,
256+ showClearIcon : Boolean = false,
257+ readOnly : Boolean = showClearIcon,
258+ selectedOption : DropDownAnswerOption ? = null,
259+ options : List <DropDownAnswerOption >,
260+ onDropDownAnswerOptionSelected : (DropDownAnswerOption ? ) -> Unit ,
261+ ) {
262+ var expanded by remember { mutableStateOf(false ) }
263+ var selectedDropDownAnswerOption by
264+ remember(selectedOption, options) { mutableStateOf(selectedOption) }
265+ var selectedOptionDisplay by
266+ remember(selectedDropDownAnswerOption) {
267+ mutableStateOf(selectedDropDownAnswerOption?.answerOptionString ? : " " )
268+ }
269+
270+ LaunchedEffect (selectedDropDownAnswerOption) {
271+ onDropDownAnswerOptionSelected(selectedDropDownAnswerOption)
272+ }
273+
274+ ExposedDropdownMenuBox (
275+ modifier = modifier,
276+ expanded = expanded,
277+ onExpandedChange = { expanded = it },
278+ ) {
279+ OutlinedTextField (
280+ value = selectedOptionDisplay,
281+ onValueChange = { selectedOptionDisplay = it },
282+ modifier =
283+ Modifier .fillMaxWidth()
284+ .testTag(DROP_DOWN_TEXT_FIELD_TAG )
285+ .semantics { if (isError) error(supportingText ? : " " ) }
286+ .menuAnchor(MenuAnchorType .PrimaryEditable , enabled),
287+ readOnly = readOnly,
288+ enabled = enabled,
289+ minLines = 1 ,
290+ isError = isError,
291+ label = { labelText?.let { Text (it) } },
292+ supportingText = { supportingText?.let { Text (it) } },
293+ leadingIcon =
294+ selectedDropDownAnswerOption?.answerOptionImage?.let {
295+ {
296+ Icon (
297+ it.toBitmap().asImageBitmap(),
298+ contentDescription = selectedOptionDisplay,
299+ modifier = Modifier .testTag(DROP_DOWN_TEXT_FIELD_LEADING_ICON_TAG ),
300+ )
301+ }
302+ },
303+ trailingIcon = {
304+ Row (verticalAlignment = Alignment .CenterVertically ) {
305+ if (showClearIcon) {
306+ IconButton (
307+ onClick = { selectedDropDownAnswerOption = null },
308+ modifier = Modifier .testTag(CLEAR_TEXT_ICON_BUTTON_TAG ),
309+ ) {
310+ Icon (painterResource(R .drawable.ic_clear), contentDescription = " clear" )
311+ }
312+ }
313+ ExposedDropdownMenuDefaults .TrailingIcon (expanded = expanded, modifier = Modifier .menuAnchor(MenuAnchorType .SecondaryEditable , enabled))
314+ }
315+ },
316+ )
317+
318+ val filteredOptions = options.filter { it.answerOptionString.contains(selectedOptionDisplay, true ) }
319+ if (filteredOptions.isNotEmpty()) {
320+ ExposedDropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
321+ filteredOptions.forEach { option ->
322+ DropdownMenuItem (
323+ modifier = Modifier .testTag(DROP_DOWN_MENU_ITEM_TAG ),
324+ text = {
325+ Text (option.answerOptionAnnotatedString(), style = MaterialTheme .typography.bodyLarge)
326+ },
327+ leadingIcon =
328+ option.answerOptionImage?.let {
329+ {
330+ Icon (
331+ it.toBitmap().asImageBitmap(),
332+ contentDescription = option.answerOptionString,
333+ )
334+ }
335+ },
336+ enabled = enabled,
337+ onClick = {
338+ selectedDropDownAnswerOption = option
339+ expanded = false
340+ },
341+ contentPadding = ExposedDropdownMenuDefaults .ItemContentPadding ,
342+ )
343+ }
344+ }
345+ }
346+ }
347+ }
348+
146349const val CLEAR_TEXT_ICON_BUTTON_TAG = " clear_field_text"
147350const val DROP_DOWN_TEXT_FIELD_TAG = " drop_down_text_field"
148351const val DROP_DOWN_TEXT_FIELD_LEADING_ICON_TAG = " drop_down_text_field_leading_icon"
0 commit comments