Fixed AutocompleteTextField, DropdownMenu just didn't work and always took focus from TextField

This commit is contained in:
dankito 2024-08-26 01:26:45 +02:00
parent 972af95a11
commit 13d922da87
4 changed files with 73 additions and 35 deletions

View File

@ -41,6 +41,7 @@ fun App() {
setTransaction(bankService.getTransactions()) setTransaction(bankService.getTransactions())
} }
MaterialTheme(colors = colors, typography = typography) { MaterialTheme(colors = colors, typography = typography) {
Box { Box {
Column( Column(

View File

@ -1,7 +1,6 @@
package net.codinux.banking.ui.dialogs package net.codinux.banking.ui.dialogs
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -25,7 +24,7 @@ private val bankingService = DI.bankingService
fun AddAccountDialog( fun AddAccountDialog(
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
var bankCode by remember { mutableStateOf("") } var selectedBank by remember { mutableStateOf<BankInfo?>(null) }
var loginName by remember { mutableStateOf("") } var loginName by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
@ -54,21 +53,15 @@ fun AddAccountDialog(
AutocompleteTextField( AutocompleteTextField(
onValueChange = { selectedBank = it }, onValueChange = { selectedBank = it },
label = { Text("Bank (Suche mit Name, Bankleitzahl oder Ort)") }, label = { Text("Bank (Suche mit Name, Bankleitzahl oder Ort)") },
getItemTitle = { bank -> bank.name },
fetchSuggestions = { query -> bankingService.findBanks(query) } fetchSuggestions = { query -> bankingService.findBanks(query) }
) { bank -> ) { bank ->
Column( Text(bank.name)
Modifier.fillMaxWidth().clickable {
selectedItem = bank
}
.padding(8.dp)
) {
Text(bank.name)
Row(Modifier.fillMaxWidth().padding(top = 8.dp)) { Row(Modifier.fillMaxWidth().padding(top = 8.dp)) {
Text(bank.bankCode) Text(bank.bankCode)
Text("${bank.postalCode} ${bank.city}", Modifier.weight(1f).padding(start = 8.dp), color = Color.Gray) Text("${bank.postalCode} ${bank.city}", Modifier.weight(1f).padding(start = 8.dp), color = Color.Gray)
}
} }
} }

View File

@ -1,35 +1,48 @@
package net.codinux.banking.ui.forms package net.codinux.banking.ui.forms
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.DropdownMenu import androidx.compose.material.Divider
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize import androidx.compose.ui.unit.toSize
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.codinux.banking.ui.service.Colors
@Composable @Composable
fun <T> AutocompleteTextField( fun <T> AutocompleteTextField(
onValueChange: (T) -> Unit, onValueChange: (T?) -> Unit,
modifier: Modifier = Modifier.fillMaxWidth(), modifier: Modifier = Modifier.fillMaxWidth(),
label: @Composable () -> Unit = { Text("Search") }, label: @Composable () -> Unit = { Text("Search") },
showDividersBetweenItems: Boolean = true,
getItemTitle: ((T) -> String)? = null,
fetchSuggestions: suspend (query: String) -> List<T> = { emptyList() }, fetchSuggestions: suspend (query: String) -> List<T> = { emptyList() },
suggestionContent: @Composable (T) -> Unit suggestionContent: @Composable (T) -> Unit
) { ) {
var searchQuery by remember { mutableStateOf("") } var searchQuery by remember { mutableStateOf("") }
var selectedItem by remember { mutableStateOf<T?>(null) }
var isLoading by remember { mutableStateOf(false) } var isLoading by remember { mutableStateOf(false) }
var suggestions by remember { mutableStateOf<List<T>>(emptyList()) } var suggestions by remember { mutableStateOf<List<T>>(emptyList()) }
var textFieldSize by remember { mutableStateOf(Size.Zero) } var textFieldSize by remember { mutableStateOf(Size.Zero) }
val expanded by remember(suggestions) { derivedStateOf { suggestions.isNotEmpty() } } var expanded by remember { mutableStateOf(false) }
val textFieldFocus = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -39,21 +52,27 @@ fun <T> AutocompleteTextField(
value = searchQuery, value = searchQuery,
onValueChange = { query -> onValueChange = { query ->
searchQuery = query searchQuery = query
selectedItem = null onValueChange(null)
if (query.length >= 2) {
if (query.length >= 1) {
isLoading = true isLoading = true
coroutineScope.launch { coroutineScope.launch {
suggestions = fetchSuggestions(query) suggestions = fetchSuggestions(query)
isLoading = false isLoading = false
expanded = true
} }
} else { } else {
suggestions = emptyList() suggestions = emptyList()
expanded = false
} }
}, },
label = label, label = label,
modifier = modifier.onGloballyPositioned { modifier = modifier
textFieldSize = it.size.toSize() .focusRequester(textFieldFocus)
}, .onGloballyPositioned {
textFieldSize = it.size.toSize()
},
trailingIcon = { trailingIcon = {
if (isLoading) { if (isLoading) {
CircularProgressIndicator( CircularProgressIndicator(
@ -67,7 +86,8 @@ fun <T> AutocompleteTextField(
modifier = Modifier.clickable { modifier = Modifier.clickable {
searchQuery = "" searchQuery = ""
suggestions = emptyList() suggestions = emptyList()
selectedItem = null expanded = false
onValueChange(null)
} }
) )
} }
@ -75,20 +95,42 @@ fun <T> AutocompleteTextField(
// isError = selectedItem == null && searchQuery.isNotEmpty() // isError = selectedItem == null && searchQuery.isNotEmpty()
) )
DropdownMenu( if (expanded) {
expanded = expanded, RoundedCornersCard(
onDismissRequest = { suggestions = emptyList() }, Modifier.width(textFieldSize.width.dp)
modifier = with(Modifier) { .heightIn(max = 400.dp)
width(textFieldSize.width.dp) .padding(top = with(LocalDensity.current) { (textFieldSize.height + 2).toDp() })
.heightIn(max = 400.dp) ,
} cornerSize = 4.dp,
) { shadowElevation = 4.dp
suggestions.forEach { item -> ) {
Column(Modifier.fillMaxWidth()) { Column(Modifier.verticalScroll(ScrollState(0), enabled = true)) {
suggestionContent(item) suggestions.forEachIndexed { index, item ->
Column(
Modifier.fillMaxWidth().padding(8.dp).clickable {
onValueChange(item)
getItemTitle?.let {
searchQuery = it.invoke(item)
}
expanded = false
focusManager.moveFocus(FocusDirection.Down)
}
) {
suggestionContent(item)
}
if (showDividersBetweenItems && index < suggestions.size - 1) {
Divider(color = Colors.Zinc200, thickness = 1.dp)
}
}
} }
} }
} }
} }
LaunchedEffect(textFieldFocus) {
textFieldFocus.requestFocus()
}
} }

View File

@ -3,12 +3,14 @@ package net.codinux.banking.ui.forms
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card import androidx.compose.material.Card
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun RoundedCornersCard(cornerSize: Dp = 12.dp, shadowElevation: Dp = 2.dp, content: @Composable () -> Unit) { fun RoundedCornersCard(modifier: Modifier = Modifier, cornerSize: Dp = 12.dp, shadowElevation: Dp = 2.dp, content: @Composable () -> Unit) {
Card( Card(
modifier,
shape = RoundedCornerShape(cornerSize), // Rounded corners shape = RoundedCornerShape(cornerSize), // Rounded corners
elevation = shadowElevation, // Shadow elevation elevation = shadowElevation, // Shadow elevation
content = content content = content