@@ -20,12 +20,12 @@ import androidx.compose.foundation.layout.Row
2020import androidx.compose.foundation.layout.fillMaxWidth
2121import androidx.compose.material3.DropdownMenuItem
2222import androidx.compose.material3.ExperimentalMaterial3Api
23+ import androidx.compose.material3.ExposedDropdownMenuAnchorType
2324import androidx.compose.material3.ExposedDropdownMenuBox
2425import androidx.compose.material3.ExposedDropdownMenuDefaults
2526import androidx.compose.material3.Icon
2627import androidx.compose.material3.IconButton
2728import androidx.compose.material3.MaterialTheme
28- import androidx.compose.material3.MenuAnchorType
2929import androidx.compose.material3.OutlinedTextField
3030import androidx.compose.material3.Text
3131import androidx.compose.runtime.Composable
@@ -43,19 +43,20 @@ import androidx.compose.ui.res.painterResource
4343import androidx.compose.ui.semantics.error
4444import androidx.compose.ui.semantics.semantics
4545import androidx.compose.ui.text.AnnotatedString
46+ import androidx.compose.ui.text.TextRange
47+ import androidx.compose.ui.text.input.TextFieldValue
4648import androidx.core.graphics.drawable.toBitmap
4749import com.google.android.fhir.datacapture.R
4850import com.google.android.fhir.datacapture.views.factories.DropDownAnswerOption
4951
5052@OptIn(ExperimentalMaterial3Api ::class )
5153@Composable
52- internal fun ExposedDropDownMenuBoxItem (
54+ internal fun DropDownItem (
5355 modifier : Modifier ,
5456 enabled : Boolean ,
5557 labelText : AnnotatedString ? = null,
5658 supportingText : String? = null,
5759 isError : Boolean = false,
58- showClearIcon : Boolean = false,
5960 selectedOption : DropDownAnswerOption ? = null,
6061 options : List <DropDownAnswerOption >,
6162 onDropDownAnswerOptionSelected : (DropDownAnswerOption ? ) -> Unit ,
@@ -84,7 +85,7 @@ internal fun ExposedDropDownMenuBoxItem(
8485 Modifier .fillMaxWidth()
8586 .testTag(DROP_DOWN_TEXT_FIELD_TAG )
8687 .semantics { if (isError) error(supportingText ? : " " ) }
87- .menuAnchor(MenuAnchorType .PrimaryNotEditable , enabled),
88+ .menuAnchor(ExposedDropdownMenuAnchorType .PrimaryNotEditable , enabled),
8889 readOnly = true ,
8990 enabled = enabled,
9091 minLines = 1 ,
@@ -101,6 +102,109 @@ internal fun ExposedDropDownMenuBoxItem(
101102 )
102103 }
103104 },
105+ trailingIcon = { ExposedDropdownMenuDefaults .TrailingIcon (expanded = expanded) },
106+ )
107+ ExposedDropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
108+ options.forEach { option ->
109+ DropDownAnswerMenuItem (enabled, option) {
110+ selectedDropDownAnswerOption = option
111+ expanded = false
112+ }
113+ }
114+ }
115+ }
116+ }
117+
118+ @OptIn(ExperimentalMaterial3Api ::class )
119+ @Composable
120+ internal fun DropDownAnswerMenuItem (
121+ enabled : Boolean ,
122+ answerOption : DropDownAnswerOption ,
123+ onSelected : () -> Unit ,
124+ ) {
125+ DropdownMenuItem (
126+ modifier = Modifier .testTag(DROP_DOWN_ANSWER_MENU_ITEM_TAG ),
127+ text = {
128+ Text (answerOption.answerOptionAnnotatedString(), style = MaterialTheme .typography.bodyLarge)
129+ },
130+ leadingIcon =
131+ answerOption.answerOptionImage?.let {
132+ {
133+ Icon (
134+ it.toBitmap().asImageBitmap(),
135+ contentDescription = answerOption.answerOptionString,
136+ )
137+ }
138+ },
139+ enabled = enabled,
140+ onClick = { onSelected() },
141+ contentPadding = ExposedDropdownMenuDefaults .ItemContentPadding ,
142+ )
143+ }
144+
145+ @OptIn(ExperimentalMaterial3Api ::class )
146+ @Composable
147+ internal fun AutoCompleteDropDownItem (
148+ modifier : Modifier ,
149+ enabled : Boolean ,
150+ labelText : AnnotatedString ? = null,
151+ supportingText : String? = null,
152+ isError : Boolean = false,
153+ showClearIcon : Boolean = false,
154+ readOnly : Boolean = showClearIcon,
155+ selectedOption : DropDownAnswerOption ? = null,
156+ options : List <DropDownAnswerOption >,
157+ onDropDownAnswerOptionSelected : (DropDownAnswerOption ? ) -> Unit ,
158+ ) {
159+ var expanded by remember { mutableStateOf(false ) }
160+ var selectedDropDownAnswerOption by
161+ remember(selectedOption, options) { mutableStateOf(selectedOption) }
162+ var selectedOptionDisplay by
163+ remember(selectedDropDownAnswerOption) {
164+ val stringValue = selectedDropDownAnswerOption?.answerOptionString ? : " "
165+ mutableStateOf(TextFieldValue (stringValue, selection = TextRange (stringValue.length)))
166+ }
167+ val filteredOptions =
168+ remember(options, selectedOptionDisplay) {
169+ options.filter { it.answerOptionString.contains(selectedOptionDisplay.text, true ) }
170+ }
171+
172+ LaunchedEffect (selectedDropDownAnswerOption) {
173+ onDropDownAnswerOptionSelected(selectedDropDownAnswerOption)
174+ }
175+
176+ ExposedDropdownMenuBox (
177+ modifier = modifier,
178+ expanded = expanded,
179+ onExpandedChange = { expanded = it },
180+ ) {
181+ OutlinedTextField (
182+ value = selectedOptionDisplay,
183+ onValueChange = {
184+ selectedOptionDisplay = it
185+ if (! expanded) expanded = true
186+ },
187+ modifier =
188+ Modifier .fillMaxWidth()
189+ .testTag(DROP_DOWN_TEXT_FIELD_TAG )
190+ .semantics { if (isError) error(supportingText ? : " " ) }
191+ .menuAnchor(ExposedDropdownMenuAnchorType .PrimaryEditable , enabled),
192+ readOnly = readOnly,
193+ enabled = enabled,
194+ minLines = 1 ,
195+ isError = isError,
196+ label = { labelText?.let { Text (it) } },
197+ supportingText = { supportingText?.let { Text (it) } },
198+ leadingIcon =
199+ selectedDropDownAnswerOption?.answerOptionImage?.let {
200+ {
201+ Icon (
202+ it.toBitmap().asImageBitmap(),
203+ contentDescription = selectedDropDownAnswerOption!! .answerOptionString,
204+ modifier = Modifier .testTag(DROP_DOWN_TEXT_FIELD_LEADING_ICON_TAG ),
205+ )
206+ }
207+ },
104208 trailingIcon = {
105209 Row (verticalAlignment = Alignment .CenterVertically ) {
106210 if (showClearIcon) {
@@ -111,33 +215,26 @@ internal fun ExposedDropDownMenuBoxItem(
111215 Icon (painterResource(R .drawable.ic_clear), contentDescription = " clear" )
112216 }
113217 }
114- ExposedDropdownMenuDefaults .TrailingIcon (expanded = expanded)
218+ ExposedDropdownMenuDefaults .TrailingIcon (
219+ expanded = expanded,
220+ modifier =
221+ Modifier .menuAnchor(
222+ ExposedDropdownMenuAnchorType .SecondaryEditable ,
223+ enabled,
224+ ),
225+ )
115226 }
116227 },
117228 )
118- ExposedDropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
119- options.forEach { option ->
120- DropdownMenuItem (
121- modifier = Modifier .testTag(DROP_DOWN_MENU_ITEM_TAG ),
122- text = {
123- Text (option.answerOptionAnnotatedString(), style = MaterialTheme .typography.bodyLarge)
124- },
125- leadingIcon =
126- option.answerOptionImage?.let {
127- {
128- Icon (
129- it.toBitmap().asImageBitmap(),
130- contentDescription = option.answerOptionString,
131- )
132- }
133- },
134- enabled = enabled,
135- onClick = {
229+
230+ if (filteredOptions.isNotEmpty()) {
231+ ExposedDropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
232+ filteredOptions.forEach { option ->
233+ DropDownAnswerMenuItem (enabled, option) {
136234 selectedDropDownAnswerOption = option
137235 expanded = false
138- },
139- contentPadding = ExposedDropdownMenuDefaults .ItemContentPadding ,
140- )
236+ }
237+ }
141238 }
142239 }
143240 }
@@ -146,4 +243,4 @@ internal fun ExposedDropDownMenuBoxItem(
146243const val CLEAR_TEXT_ICON_BUTTON_TAG = " clear_field_text"
147244const val DROP_DOWN_TEXT_FIELD_TAG = " drop_down_text_field"
148245const val DROP_DOWN_TEXT_FIELD_LEADING_ICON_TAG = " drop_down_text_field_leading_icon"
149- const val DROP_DOWN_MENU_ITEM_TAG = " drop_down_list_menu_item "
246+ const val DROP_DOWN_ANSWER_MENU_ITEM_TAG = " drop_down_answer_list_menu_item "
0 commit comments