Autocomplete tag input

This commit is contained in:
MM20 2022-09-22 22:22:40 +02:00
parent 5073cb0297
commit 060e2975aa
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
5 changed files with 102 additions and 80 deletions

View File

@ -30,6 +30,8 @@ interface CustomAttributesRepository {
suspend fun export(toDir: File)
suspend fun import(fromDir: File)
suspend fun getAllTags(startsWith: String? = null): List<String>
}
internal class CustomAttributesRepositoryImpl(
@ -103,6 +105,15 @@ internal class CustomAttributesRepositoryImpl(
}
}
override suspend fun getAllTags(startsWith: String?): List<String> {
val dao = appDatabase.customAttrsDao()
return if (startsWith != null) {
dao.getAllTagsLike("$startsWith%")
} else {
dao.getAllTags()
}
}
override suspend fun search(query: String): Flow<List<Searchable>> {
if (query.isBlank()) {
return flow {

View File

@ -32,4 +32,10 @@ interface CustomAttrsDao {
clearCustomAttribute(key, "tag")
insertCustomAttributes(tags)
}
@Query("SELECT DISTINCT value FROM CustomAttributes WHERE type = 'tag' AND value LIKE :like ORDER BY value")
suspend fun getAllTagsLike(like: String): List<String>
@Query("SELECT DISTINCT value FROM CustomAttributes WHERE type = 'tag' ORDER BY value")
suspend fun getAllTags(): List<String>
}

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@ -35,6 +36,7 @@ fun OutlinedTagsInputField(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
textColor: Color = LocalContentColor.current,
onAutocomplete: (suspend (query: String) -> List<String>)? = null
) {
var value by remember { mutableStateOf("") }
var lastTagFocused by remember { mutableStateOf(false) }
@ -42,6 +44,8 @@ fun OutlinedTagsInputField(
val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()
var completions by remember(onAutocomplete) { mutableStateOf<List<String>>(emptyList()) }
BasicTextField(
modifier = modifier
.onKeyEvent {
@ -73,6 +77,15 @@ fun OutlinedTagsInputField(
onTagsChange(tags + newTags.dropLast(1).filter { it.isNotBlank() })
}
value = newTags.last()
if (value.isNotBlank()) {
onAutocomplete?.let {
scope.launch {
completions = it(value)
}
}
} else {
completions = emptyList()
}
lastTagFocused = false
},
textStyle = textStyle.copy(
@ -90,96 +103,81 @@ fun OutlinedTagsInputField(
TextFieldDefaults.OutlinedTextFieldDecorationBox(
contentPadding = PaddingValues(0.dp),
value = value,
innerTextField = { Box {
Row(
modifier = Modifier.horizontalScroll(rememberScrollState()).padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
for ((i, tag) in tags.withIndex()) {
InputChip(
selected = i == tags.lastIndex && lastTagFocused,
modifier = Modifier.padding(end = 12.dp),
onClick = { },
leadingIcon = {
Icon(imageVector = Icons.Rounded.Tag, contentDescription = null)
},
label = { Text(tag) },
trailingIcon = {
Icon(
modifier = Modifier.clickable {
onTagsChange(tags.filter { it != tag })
}, imageVector = Icons.Rounded.Clear, contentDescription = null
)
},
)
}
Box(
innerTextField = {
Box {
Row(
modifier = Modifier
.height(56.dp),
contentAlignment = Alignment.CenterStart
.horizontalScroll(rememberScrollState())
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (value.isEmpty()) {
CompositionLocalProvider(
LocalTextStyle provides textStyle,
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
for ((i, tag) in tags.withIndex()) {
InputChip(
selected = i == tags.lastIndex && lastTagFocused,
modifier = Modifier.padding(end = 12.dp),
onClick = { },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Tag,
contentDescription = null
)
},
label = { Text(tag) },
trailingIcon = {
Icon(
modifier = Modifier.clickable {
onTagsChange(tags.filterIndexed { index, _ -> index != i })
},
imageVector = Icons.Rounded.Clear,
contentDescription = null
)
},
)
}
Box(
modifier = Modifier
.height(56.dp),
contentAlignment = Alignment.CenterStart
) {
if (value.isEmpty()) {
CompositionLocalProvider(
LocalTextStyle provides textStyle,
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
) {
placeholder?.invoke()
}
}
innerTextField()
}
}
if (completions.isNotEmpty()) {
Box(modifier = Modifier.fillMaxWidth()) {
DropdownMenu(
expanded = true,
onDismissRequest = { completions = emptyList() },
properties = PopupProperties(focusable = false)
) {
placeholder?.invoke()
for (completion in completions) {
DropdownMenuItem(
text = { Text(completion) },
onClick = {
onTagsChange(tags + completion)
value = ""
completions = emptyList()
},
)
}
}
}
innerTextField()
}
}
} },
},
enabled = true,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
)
/*Box {
Row(
modifier = Modifier.horizontalScroll(rememberScrollState()),
verticalAlignment = Alignment.CenterVertically
) {
for ((i, tag) in tags.withIndex()) {
InputChip(
selected = i == tags.lastIndex && lastTagFocused,
modifier = Modifier.padding(end = 12.dp),
onClick = { },
leadingIcon = {
Icon(imageVector = Icons.Rounded.Tag, contentDescription = null)
},
label = { Text(tag) },
trailingIcon = {
Icon(
modifier = Modifier.clickable {
onTagsChange(tags.filter { it != tag })
}, imageVector = Icons.Rounded.Clear, contentDescription = null
)
},
)
}
Box(
modifier = Modifier
.padding(vertical = 12.dp)
.padding(
start = if (tags.isEmpty()) 16.dp else 0.dp,
end = 16.dp
)
) {
if (value.isEmpty()) {
CompositionLocalProvider(
LocalTextStyle provides textStyle,
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
) {
placeholder?.invoke()
}
}
innerTextField()
}
}
}*/
},
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary)
)

View File

@ -124,11 +124,14 @@ fun CustomizeSearchableSheet(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
tags = tags, onTagsChange = { tags = it },
tags = tags, onTagsChange = { tags = it.distinct() },
placeholder = {
Text(stringResource(R.string.customize_tags_placeholder))
},
textStyle = MaterialTheme.typography.bodyMedium
textStyle = MaterialTheme.typography.bodyMedium,
onAutocomplete = {
viewModel.autocompleteTags(it).minus(tags.toSet())
}
)
DisposableEffect(searchable.key) {

View File

@ -84,4 +84,8 @@ class CustomizeSearchableSheetVM(
fun getTags(): Flow<List<String>> {
return customAttributesRepository.getTags(searchable)
}
suspend fun autocompleteTags(query: String): List<String> {
return customAttributesRepository.getAllTags(startsWith = query)
}
}