Add authors suggestion and update search suggestion ui

This commit is contained in:
Koitharu
2024-04-24 10:41:44 +03:00
parent 56892aea3c
commit daa545f3db
15 changed files with 96 additions and 19 deletions

View File

@@ -28,6 +28,9 @@ abstract class MangaDao {
@Query("SELECT * FROM manga WHERE source = :source")
abstract suspend fun findAllBySource(source: String): List<MangaWithTags>
@Query("SELECT author FROM manga WHERE author LIKE :query GROUP BY author ORDER BY COUNT(author) DESC LIMIT :limit")
abstract suspend fun findAuthors(query: String, limit: Int): List<String>
@Transaction
@Query("SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit")
abstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>

View File

@@ -12,4 +12,5 @@ enum class SearchSuggestionType(
QUERIES_SUGGEST(R.string.suggested_queries),
MANGA(R.string.content_type_manga),
SOURCES(R.string.remote_sources),
AUTHORS(R.string.authors),
}

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTag
@@ -35,19 +36,17 @@ class MangaSearchRepository @Inject constructor(
) {
suspend fun getMangaSuggestion(query: String, limit: Int, source: MangaSource?): List<Manga> {
if (query.isEmpty()) {
return emptyList()
}
val skipNsfw = settings.isNsfwContentDisabled
return if (source != null) {
db.getMangaDao().searchByTitle("%$query%", source.name, limit)
} else {
db.getMangaDao().searchByTitle("%$query%", limit)
return when {
query.isEmpty() -> db.getSuggestionDao().getRandom(limit).map { MangaWithTags(it.manga, it.tags) }
source != null -> db.getMangaDao().searchByTitle("%$query%", source.name, limit)
else -> db.getMangaDao().searchByTitle("%$query%", limit)
}.let {
if (skipNsfw) it.filterNot { x -> x.manga.isNsfw } else it
if (settings.isNsfwContentDisabled) it.filterNot { x -> x.manga.isNsfw } else it
}.map {
it.toManga()
}.sortedBy { x ->
x.title.levenshteinDistance(query)
}
.map { it.toManga() }
.sortedBy { x -> x.title.levenshteinDistance(query) }
}
suspend fun getQuerySuggestion(
@@ -90,10 +89,21 @@ class MangaSearchRepository @Inject constructor(
return titles.shuffled().take(limit)
}
suspend fun getAuthorsSuggestion(
query: String,
limit: Int,
): List<String> {
if (query.isEmpty()) {
return emptyList()
}
return db.getMangaDao().findAuthors("$query%", limit)
}
suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List<MangaTag> {
return when {
query.isNotEmpty() && source != null -> db.getTagsDao()
.findTags(source.name, "%$query%", limit)
query.isNotEmpty() -> db.getTagsDao().findTags("%$query%", limit)
source != null -> db.getTagsDao().findPopularTags(source.name, limit)
else -> db.getTagsDao().findPopularTags(limit)

View File

@@ -31,9 +31,10 @@ import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import javax.inject.Inject
private const val DEBOUNCE_TIMEOUT = 500L
private const val MAX_MANGA_ITEMS = 6
private const val MAX_MANGA_ITEMS = 12
private const val MAX_QUERY_ITEMS = 16
private const val MAX_HINTS_ITEMS = 3
private const val MAX_AUTHORS_ITEMS = 2
private const val MAX_TAGS_ITEMS = 8
private const val MAX_SOURCES_ITEMS = 6
@@ -128,6 +129,11 @@ class SearchSuggestionViewModel @Inject constructor(
} else {
null
}
val authorsDeferred = if (SearchSuggestionType.AUTHORS in types) {
async { repository.getAuthorsSuggestion(searchQuery, MAX_AUTHORS_ITEMS) }
} else {
null
}
val tagsDeferred = if (SearchSuggestionType.GENRES in types) {
async { repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null) }
} else {
@@ -148,8 +154,9 @@ class SearchSuggestionViewModel @Inject constructor(
val mangaList = mangaDeferred?.await()
val queries = queriesDeferred?.await()
val hints = hintsDeferred?.await()
val authors = authorsDeferred?.await()
buildList(queries.sizeOrZero() + sources.sizeOrZero() + hints.sizeOrZero() + 2) {
buildList(queries.sizeOrZero() + sources.sizeOrZero() + authors.sizeOrZero() + hints.sizeOrZero() + 2) {
if (!tags.isNullOrEmpty()) {
add(SearchSuggestionItem.Tags(mapTags(tags)))
}
@@ -158,6 +165,7 @@ class SearchSuggestionViewModel @Inject constructor(
}
sources?.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) }
queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
authors?.mapTo(this) { SearchSuggestionItem.Author(it) }
hints?.mapTo(this) { SearchSuggestionItem.Hint(it) }
}
}

View File

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

View File

@@ -0,0 +1,26 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import android.view.View
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryHintBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
fun searchSuggestionAuthorAD(
listener: SearchSuggestionListener,
) = adapterDelegateViewBinding<SearchSuggestionItem.Author, SearchSuggestionItem, ItemSearchSuggestionQueryHintBinding>(
{ inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) },
) {
val viewClickListener = View.OnClickListener { _ ->
listener.onQueryClick(item.name, true)
}
binding.root.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_user, 0, 0, 0)
binding.root.setOnClickListener(viewClickListener)
bind {
binding.root.text = item.name
}
}

View File

@@ -9,6 +9,7 @@ import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
@@ -59,6 +60,7 @@ private fun searchSuggestionMangaGridAD(
binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run {
defaultPlaceholders(context)
allowRgb565(true)
transformations(TrimTransformation())
source(item.source)
enqueueWith(coil)
}

View File

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

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:windowBackground" />
<item
android:drawable="@drawable/divider_horizontal"
android:gravity="top" />
</layer-list>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="1dp" />
<solid android:color="?colorSurfaceDim" />
</shape>

View File

@@ -5,10 +5,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground"
android:background="@drawable/bg_search_suggestion"
android:clickable="true"
android:clipToPadding="false"
android:orientation="vertical"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:ignore="KeyboardInaccessibleWidget" />
tools:ignore="KeyboardInaccessibleWidget" />

View File

@@ -3,10 +3,9 @@
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"
style="@style/Widget.Material3.CardView.Outlined"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:contentPadding="4dp"
app:shapeAppearance="?shapeAppearanceCornerSmall"
tools:layout_height="@dimen/search_suggestions_manga_height">
@@ -32,6 +31,7 @@
android:elegantTextHeight="false"
android:ellipsize="end"
android:lines="1"
android:padding="4dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
tools:text="@tools:sample/lorem" />

View File

@@ -12,7 +12,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingTop="6dp"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:paddingBottom="12dp"
app:singleLine="true" />
</HorizontalScrollView>

View File

@@ -36,8 +36,8 @@
<dimen name="side_card_offset">8dp</dimen>
<dimen name="webtoon_pages_gap">24dp</dimen>
<dimen name="search_suggestions_manga_height">124dp</dimen>
<dimen name="search_suggestions_manga_spacing">4dp</dimen>
<dimen name="search_suggestions_manga_height">142dp</dimen>
<dimen name="search_suggestions_manga_spacing">6dp</dimen>
<dimen name="card_indicator_size">32dp</dimen>
<dimen name="card_indicator_size_small">24dp</dimen>

View File

@@ -639,4 +639,5 @@
<string name="search_suggestions">Search suggestions</string>
<string name="recent_queries">Recent queries</string>
<string name="suggested_queries">Suggested queries</string>
<string name="authors">Authors</string>
</resources>