diff --git a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt index acd9df21..35a53f57 100644 --- a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt +++ b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt @@ -30,6 +30,8 @@ interface CustomAttributesRepository { suspend fun export(toDir: File) suspend fun import(fromDir: File) + + suspend fun getAllTags(startsWith: String? = null): List } internal class CustomAttributesRepositoryImpl( @@ -103,6 +105,15 @@ internal class CustomAttributesRepositoryImpl( } } + override suspend fun getAllTags(startsWith: String?): List { + val dao = appDatabase.customAttrsDao() + return if (startsWith != null) { + dao.getAllTagsLike("$startsWith%") + } else { + dao.getAllTags() + } + } + override suspend fun search(query: String): Flow> { if (query.isBlank()) { return flow { diff --git a/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt b/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt index 5ee3f8a3..54ada72d 100644 --- a/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt +++ b/database/src/main/java/de/mm20/launcher2/database/CustomAttrsDao.kt @@ -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 + + @Query("SELECT DISTINCT value FROM CustomAttributes WHERE type = 'tag' ORDER BY value") + suspend fun getAllTags(): List } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt index d3c25d9d..122cc1a4 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt @@ -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)? = 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>(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) ) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt index 349e00cd..0e5939e3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt @@ -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) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt index cccb7029..7af52c9c 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt @@ -84,4 +84,8 @@ class CustomizeSearchableSheetVM( fun getTags(): Flow> { return customAttributesRepository.getTags(searchable) } + + suspend fun autocompleteTags(query: String): List { + return customAttributesRepository.getAllTags(startsWith = query) + } } \ No newline at end of file