Improve search suggestions

This commit is contained in:
Koitharu
2023-08-19 15:06:42 +03:00
parent 856524c9f8
commit bc488a6878
10 changed files with 104 additions and 8 deletions

View File

@@ -73,6 +73,20 @@ class MangaSearchRepository @Inject constructor(
}.orEmpty()
}
suspend fun getQueryHintSuggestion(
query: String,
limit: Int,
): List<String> {
if (query.isEmpty()) {
return emptyList()
}
val titles = db.suggestionDao.getTitles("$query%")
if (titles.isEmpty()) {
return emptyList()
}
return titles.shuffled().take(limit)
}
suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List<MangaTag> {
return when {
query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit)

View File

@@ -69,6 +69,11 @@ class SearchSuggestionFragment :
viewModel.deleteQuery(query)
}
override fun onResume() {
super.onResume()
viewModel.onResume()
}
companion object {
fun newInstance() = SearchSuggestionFragment()

View File

@@ -30,6 +30,7 @@ import javax.inject.Inject
private const val DEBOUNCE_TIMEOUT = 500L
private const val MAX_MANGA_ITEMS = 6
private const val MAX_QUERY_ITEMS = 16
private const val MAX_HINTS_ITEMS = 3
private const val MAX_TAGS_ITEMS = 8
private const val MAX_SOURCES_ITEMS = 6
@@ -42,6 +43,7 @@ class SearchSuggestionViewModel @Inject constructor(
private val query = MutableStateFlow("")
private var suggestionJob: Job? = null
private var invalidateOnResume = false
val isIncognitoModeEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
@@ -60,11 +62,10 @@ class SearchSuggestionViewModel @Inject constructor(
}
fun saveQuery(query: String) {
launchJob(Dispatchers.Default) {
if (!settings.isIncognitoModeEnabled) {
repository.saveSearchQuery(query)
}
if (!settings.isIncognitoModeEnabled) {
repository.saveSearchQuery(query)
}
invalidateOnResume = true
}
fun clearSearchHistory() {
@@ -80,6 +81,13 @@ class SearchSuggestionViewModel @Inject constructor(
}
}
fun onResume() {
if (invalidateOnResume) {
invalidateOnResume = false
setupSuggestion()
}
}
fun deleteQuery(query: String) {
launchJob {
repository.deleteSearchQuery(query)
@@ -108,6 +116,9 @@ class SearchSuggestionViewModel @Inject constructor(
val queriesDeferred = async {
repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS)
}
val hintsDeferred = async {
repository.getQueryHintSuggestion(searchQuery, MAX_HINTS_ITEMS)
}
val tagsDeferred = async {
repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null)
}
@@ -119,16 +130,18 @@ class SearchSuggestionViewModel @Inject constructor(
val tags = tagsDeferred.await()
val mangaList = mangaDeferred.await()
val queries = queriesDeferred.await()
val hints = hintsDeferred.await()
buildList(queries.size + sources.size + 2) {
buildList(queries.size + sources.size + hints.size + 2) {
if (tags.isNotEmpty()) {
add(SearchSuggestionItem.Tags(mapTags(tags)))
}
if (mangaList.isNotEmpty()) {
add(SearchSuggestionItem.MangaList(mangaList))
}
queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
sources.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) }
queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
hints.mapTo(this) { SearchSuggestionItem.Hint(it) }
}
}

View File

@@ -20,5 +20,6 @@ class SearchSuggestionAdapter(
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionTagsAD(listener))
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionQueryHintAD(listener))
}
}

View File

@@ -0,0 +1,24 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import android.view.View
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryHintBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
fun searchSuggestionQueryHintAD(
listener: SearchSuggestionListener,
) = adapterDelegateViewBinding<SearchSuggestionItem.Hint, SearchSuggestionItem, ItemSearchSuggestionQueryHintBinding>(
{ inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) },
) {
val viewClickListener = View.OnClickListener { v ->
listener.onQueryClick(item.query, true)
}
binding.root.setOnClickListener(viewClickListener)
bind {
binding.root.text = item.query
}
}

View File

@@ -27,6 +27,15 @@ sealed interface SearchSuggestionItem : ListModel {
}
}
data class Hint(
val query: String,
) : SearchSuggestionItem {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Hint && query == other.query
}
}
data class Source(
val source: MangaSource,
val isEnabled: Boolean,

View File

@@ -26,6 +26,9 @@ abstract class SuggestionDao {
@Query("SELECT COUNT(*) FROM suggestions")
abstract suspend fun count(): Int
@Query("SELECT manga.title FROM suggestions LEFT JOIN manga ON suggestions.manga_id = manga.manga_id WHERE manga.title LIKE :query")
abstract suspend fun getTitles(query: String): List<String>
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(entity: SuggestionEntity): Long