Refine Tagger Dialog (#1969)
* Refine Tagger Dialog * Fix limit * Suggestions use sequence * Update app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> * Refine * Refine * Refine * Naming --------- Co-authored-by: Dude so hot <djohn@fbi.gov> Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
This commit is contained in:
3 files changed
+47
-47
No files matched your search
@@ -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<String, String>, keyword: String, translate: Boolean, exactly: Boolean) = flow {
|
||||
private fun rawSuggestSequence(tags: Map<String, String>, 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<Context>())
|
||||
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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ fun SearchBarScreen(
|
||||
EhTagDatabase.suggestions(
|
||||
query.substringAfterLast(' '),
|
||||
Settings.showTagTranslations,
|
||||
).collect { (tag, hint) ->
|
||||
).forEach { (tag, hint) ->
|
||||
emit(TagSuggestion(hint, tag))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Pair<String, String?>>?>(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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user