diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt index b3e38dad6..d81fa9e1a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhTagDatabase.kt @@ -36,8 +36,6 @@ import java.io.File import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -64,22 +62,22 @@ object EhTagDatabase : CoroutineScope { fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? = tagGroups[prefix]?.get(tag)?.trim()?.ifEmpty { null } - private fun internalSuggestFlow(tags: Map, keyword: String, translate: Boolean, exactly: Boolean) = flow { + private fun rawSuggestSequence(tags: Map, keyword: String, translate: Boolean, exactly: Boolean) = sequence { if (exactly) { tags[keyword]?.let { - emit(keyword to it.takeIf { translate }) + yield(keyword to it.takeIf { translate }) } } else { if (translate) { tags.forEach { (tag, hint) -> if (tag != keyword && (tag.containsIgnoreSpace(keyword) || hint.containsIgnoreSpace(keyword))) { - emit(tag to hint) + yield(tag to hint) } } } else { tags.keys.forEach { tag -> if (tag != keyword && tag.containsIgnoreSpace(keyword)) { - emit(tag to null) + yield(tag to null) } } } @@ -87,15 +85,15 @@ object EhTagDatabase : CoroutineScope { } context(Context) - fun suggestions(keyword: String, translate: Boolean) = flow { + fun suggestions(keyword: String, translate: Boolean) = sequence { val doTranslate = translate && isTranslatable(implicit()) if (initialized) { - emitAll(suggestFlow(keyword, doTranslate, true)) - emitAll(suggestFlow(keyword, doTranslate, false)) + yieldAll(suggestSequence(keyword, doTranslate, true)) + yieldAll(suggestSequence(keyword, doTranslate, false)) } } - private fun suggestFlow(keyword: String, translate: Boolean, exactly: Boolean) = flow { + private fun suggestSequence(keyword: String, translate: Boolean, exactly: Boolean) = sequence { val kwd = PREFIXES.fold(keyword) { kwd, pfx -> kwd.removePrefix(pfx) } val prefix = keyword.dropLast(kwd.length) val ns = kwd.substringBefore(':') @@ -103,18 +101,12 @@ object EhTagDatabase : CoroutineScope { val namespacePrefix = TagNamespace.from(ns)?.prefix ?: ns val tags = tagGroups[namespacePrefix.takeIf { tag.isNotEmpty() && it != NAMESPACE_PREFIX }] tags?.let { - internalSuggestFlow(it, tag, translate, exactly).collect { (tag, hint) -> - emit("$prefix$namespacePrefix:$tag" to hint) - } + yieldAll(rawSuggestSequence(it, tag, translate, exactly).map { (tag, hint) -> "$prefix$namespacePrefix:$tag" to hint }) } ?: tagGroups.forEach { (namespacePrefix, tags) -> if (namespacePrefix != NAMESPACE_PREFIX) { - internalSuggestFlow(tags, kwd, translate, exactly).collect { (tag, hint) -> - emit("$prefix$namespacePrefix:$tag" to hint) - } + yieldAll(rawSuggestSequence(tags, kwd, translate, exactly).map { (tag, hint) -> "$prefix$namespacePrefix:$tag" to hint }) } else { - internalSuggestFlow(tags, kwd, translate, exactly).collect { (ns, hint) -> - emit("$prefix$ns:" to hint) - } + yieldAll(rawSuggestSequence(tags, kwd, translate, exactly).map { (ns, hint) -> "$prefix$ns:" to hint }) } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt index 36ccdd257..1b4cdf81a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt @@ -152,7 +152,7 @@ fun SearchBarScreen( EhTagDatabase.suggestions( query.substringAfterLast(' '), Settings.showTagTranslations, - ).collect { (tag, hint) -> + ).forEach { (tag, hint) -> emit(TagSuggestion(hint, tag)) } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt index 698ed32f5..50937a995 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn @@ -24,6 +25,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -99,8 +101,6 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.take -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -253,35 +253,43 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M }, ) val query = state.text.toString().trim().takeIf { s -> s.isNotEmpty() } - Await(suggestionTranslate, { query?.let { suggestions(query, suggestionTranslate).take(4).toList() } }) { items -> - if (!items.isNullOrEmpty()) { - ExposedDropdownMenu( - expanded = true, - onDismissRequest = { }, - ) { - items.forEach { (tag, hint) -> - DropdownMenuItem( - text = { - Column { - Text(text = tag, maxLines = 1) - ProvideTextStyle(MaterialTheme.typography.bodySmall) { - if (hint != null) { - Text( - text = hint, - maxLines = 1, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + val items = remember { mutableStateOf>?>(null) } + LaunchedEffect(suggestionTranslate, query) { + items.value = query?.let { suggestions(query, suggestionTranslate).take(15).toList() } + } + val itemsNow = items.value + ExposedDropdownMenu( + expanded = !itemsNow.isNullOrEmpty(), + onDismissRequest = { }, + modifier = Modifier.heightIn(max = 192.dp), + ) { + if (!itemsNow.isNullOrEmpty()) { + itemsNow.forEach { (tag, hint) -> + DropdownMenuItem( + text = { + Column { + Text(text = tag, maxLines = 1) + ProvideTextStyle(MaterialTheme.typography.bodySmall) { + if (hint != null) { + Text( + text = hint, + maxLines = 1, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } - }, - onClick = { + } + }, + onClick = { + if (tag.endsWith(':')) { + state.setTextAndPlaceCursorAtEnd(tag) + } else { selected += tag state.clearText() - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } + } + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) } } }