From ed8729292105819de04467090e9ec692b48331c2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 4 Oct 2023 12:25:09 +0300 Subject: [PATCH] Adaptive tags suggestion --- .../koitharu/kotatsu/core/db/dao/TagsDao.kt | 22 +++++++++++++++++++ .../kotatsu/core/db/entity/EntityMapping.kt | 2 ++ .../kotatsu/filter/ui/FilterCoordinator.kt | 13 +++++++---- .../search/domain/MangaSearchRepository.kt | 16 ++++++++++++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt index bde4641d6..beded29b3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt @@ -51,6 +51,28 @@ abstract class TagsDao { ) abstract suspend fun findTags(query: String, limit: Int): List + @Query( + """ + SELECT tags.* FROM manga_tags + LEFT JOIN tags ON tags.tag_id = manga_tags.tag_id + WHERE manga_tags.manga_id IN (SELECT manga_id FROM manga_tags WHERE tag_id = :tagId) + GROUP BY tags.tag_id + ORDER BY COUNT(manga_id) DESC; + """, + ) + abstract suspend fun findRelatedTags(tagId: Long): List + + @Query( + """ + SELECT tags.* FROM manga_tags + LEFT JOIN tags ON tags.tag_id = manga_tags.tag_id + WHERE manga_tags.manga_id IN (SELECT manga_id FROM manga_tags WHERE tag_id IN (:ids)) + GROUP BY tags.tag_id + ORDER BY COUNT(manga_id) DESC; + """, + ) + abstract suspend fun findRelatedTags(ids: Set): List + @Upsert abstract suspend fun upsert(tags: Iterable) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt index 80bcb6045..d4280c850 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt @@ -19,6 +19,8 @@ fun TagEntity.toMangaTag() = MangaTag( fun Collection.toMangaTags() = mapToSet(TagEntity::toMangaTag) +fun Collection.toMangaTagsList() = map(TagEntity::toMangaTag) + fun MangaEntity.toManga(tags: Set) = Manga( id = this.id, title = this.title, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt index dba254833..700afcd86 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt @@ -131,7 +131,7 @@ class FilterCoordinator @Inject constructor( observeState(), observeAvailableTags(), ) { state, available -> - val chips = createChipsList(state, available.orEmpty()) + val chips = createChipsList(state, available.orEmpty(), 8) FilterHeaderModel(chips, state.sortOrder, state.tags.isNotEmpty()) } @@ -157,11 +157,16 @@ class FilterCoordinator @Inject constructor( private suspend fun createChipsList( filterState: FilterState, availableTags: Set, + limit: Int, ): List { val selectedTags = filterState.tags.toMutableSet() - var tags = searchRepository.getTagsSuggestion("", 6, repository.source) - if (tags.isEmpty()) { - tags = availableTags.take(6) + var tags = if (selectedTags.isEmpty()) { + searchRepository.getTagsSuggestion("", limit, repository.source) + } else { + searchRepository.getTagsSuggestion(selectedTags).take(limit) + } + if (tags.size < limit) { + tags = tags + availableTags.take(limit - tags.size) } if (tags.isEmpty() && selectedTags.isEmpty()) { return emptyList() 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 b6da6d6f6..4eca29a0d 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,8 +10,10 @@ 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.toEntity import org.koitharu.kotatsu.core.db.entity.toManga import org.koitharu.kotatsu.core.db.entity.toMangaTag +import org.koitharu.kotatsu.core.db.entity.toMangaTagsList import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.parsers.model.ContentType @@ -19,6 +21,7 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.util.levenshteinDistance +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider import javax.inject.Inject @@ -93,8 +96,17 @@ class MangaSearchRepository @Inject constructor( query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit) source != null -> db.tagsDao.findPopularTags(source.name, limit) else -> db.tagsDao.findPopularTags(limit) - }.map { - it.toMangaTag() + }.toMangaTagsList() + } + + suspend fun getTagsSuggestion(tags: Set): List { + val ids = tags.mapToSet { it.toEntity().id } + return if (ids.size == 1) { + db.tagsDao.findRelatedTags(ids.first()) + } else { + db.tagsDao.findRelatedTags(ids) + }.mapNotNull { x -> + if (x.id in ids) null else x.toMangaTag() } }