Improve search suggestions
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -69,6 +69,11 @@ class SearchSuggestionFragment :
|
||||
viewModel.deleteQuery(query)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = SearchSuggestionFragment()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,5 +20,6 @@ class SearchSuggestionAdapter(
|
||||
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(searchSuggestionTagsAD(listener))
|
||||
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(searchSuggestionQueryHintAD(listener))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd">
|
||||
|
||||
@@ -24,6 +25,12 @@
|
||||
app:drawableStartCompat="@drawable/ic_history"
|
||||
tools:text="@tools:sample/lorem[6]" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:layout_marginHorizontal="4dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_complete"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -33,4 +40,4 @@
|
||||
android:src="@drawable/abc_ic_commit_search_api_mtrl_alpha"
|
||||
app:tint="?colorControlNormal" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawablePadding="12dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
app:drawableStartCompat="@drawable/ic_suggestion"
|
||||
tools:text="@tools:sample/lorem[6]" />
|
||||
@@ -4,8 +4,9 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="4dp"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:paddingBottom="6dp"
|
||||
app:chipSpacingHorizontal="6dp"
|
||||
app:chipSpacingVertical="6dp" />
|
||||
|
||||
Reference in New Issue
Block a user