diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt index 7ee5567b0..2189cfc31 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt @@ -28,6 +28,9 @@ abstract class MangaDao { @Query("SELECT * FROM manga WHERE source = :source") abstract suspend fun findAllBySource(source: String): List + @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 + @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 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt index b0229ebbd..ab3cb3c49 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt @@ -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), } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index e2b474ee4..3adc10d2a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -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 { - 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 { + if (query.isEmpty()) { + return emptyList() + } + return db.getMangaDao().findAuthors("$query%", limit) + } + suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List { 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) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index 61a982d22..0aff417cb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -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) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index 3140500b2..3152927f0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -21,5 +21,6 @@ class SearchSuggestionAdapter( .addDelegate(searchSuggestionTagsAD(listener)) .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener)) .addDelegate(searchSuggestionQueryHintAD(listener)) + .addDelegate(searchSuggestionAuthorAD(listener)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAuthorAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAuthorAD.kt new file mode 100644 index 000000000..f59b0d785 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAuthorAD.kt @@ -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( + { 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 + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt index 382a97b1b..789704e90 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt @@ -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) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index f7b83531c..5493fb920 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -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, diff --git a/app/src/main/res/drawable/bg_search_suggestion.xml b/app/src/main/res/drawable/bg_search_suggestion.xml new file mode 100644 index 000000000..72d982909 --- /dev/null +++ b/app/src/main/res/drawable/bg_search_suggestion.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/divider_horizontal.xml b/app/src/main/res/drawable/divider_horizontal.xml new file mode 100644 index 000000000..08db536c1 --- /dev/null +++ b/app/src/main/res/drawable/divider_horizontal.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_search_suggestion.xml b/app/src/main/res/layout/fragment_search_suggestion.xml index c303d7867..a5530e339 100644 --- a/app/src/main/res/layout/fragment_search_suggestion.xml +++ b/app/src/main/res/layout/fragment_search_suggestion.xml @@ -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" /> \ No newline at end of file + tools:ignore="KeyboardInaccessibleWidget" /> diff --git a/app/src/main/res/layout/item_search_suggestion_manga_grid.xml b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml index 33f07c97e..dc96d316a 100644 --- a/app/src/main/res/layout/item_search_suggestion_manga_grid.xml +++ b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml @@ -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" /> diff --git a/app/src/main/res/layout/item_search_suggestion_tags.xml b/app/src/main/res/layout/item_search_suggestion_tags.xml index 1879dc239..27a3eaf89 100644 --- a/app/src/main/res/layout/item_search_suggestion_tags.xml +++ b/app/src/main/res/layout/item_search_suggestion_tags.xml @@ -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" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 081fb6f85..fa3c2f89a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -36,8 +36,8 @@ 8dp 24dp - 124dp - 4dp + 142dp + 6dp 32dp 24dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1196900be..4c63e545c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -639,4 +639,5 @@ Search suggestions Recent queries Suggested queries + Authors