From c8a8203c39c57906e8ecda39a859ef3ae071d57c Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 21 Oct 2025 14:40:37 +0300 Subject: [PATCH] Add authors to filter --- .../koitharu/kotatsu/core/db/dao/MangaDao.kt | 3 +++ .../kotatsu/filter/ui/FilterCoordinator.kt | 14 +++++++++++ .../filter/ui/sheet/FilterSheetFragment.kt | 23 +++++++++++++++++++ .../search/domain/MangaSearchRepository.kt | 4 ++++ app/src/main/res/layout/sheet_filter.xml | 18 +++++++++++++++ 5 files changed, 62 insertions(+) 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 acc0eaa74..d73fd3810 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 @@ -34,6 +34,9 @@ abstract class MangaDao { @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 + @Query("SELECT author FROM manga WHERE manga.source = :source AND author IS NOT NULL AND author != '' GROUP BY author ORDER BY COUNT(author) DESC LIMIT :limit") + abstract suspend fun findAuthorsBySource(source: 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/filter/ui/FilterCoordinator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt index b1896a086..04aabfb51 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 @@ -124,6 +124,20 @@ class FilterCoordinator @Inject constructor( MutableStateFlow(FilterProperty.EMPTY) } + val authors: StateFlow> = if (capabilities.isAuthorSearchSupported) { + combine( + flow { emit(searchRepository.getAuthors(repository.source, TAGS_LIMIT)) }, + currentListFilter.distinctUntilChangedBy { it.author }, + ) { available, selected -> + FilterProperty( + availableItems = available, + selectedItems = setOfNotNull(selected.author), + ) + }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING) + } else { + MutableStateFlow(FilterProperty.EMPTY) + } + val states: StateFlow> = combine( filterOptions.asFlow(), currentListFilter.distinctUntilChangedBy { it.states }, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt index 0e34cb1b4..2b6711486 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt @@ -78,6 +78,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), filter.originalLocale.observe(viewLifecycleOwner, this::onOriginalLocaleChanged) filter.tags.observe(viewLifecycleOwner, this::onTagsChanged) filter.tagsExcluded.observe(viewLifecycleOwner, this::onTagsExcludedChanged) + filter.authors.observe(viewLifecycleOwner, this::onAuthorsChanged) filter.states.observe(viewLifecycleOwner, this::onStateChanged) filter.contentTypes.observe(viewLifecycleOwner, this::onContentTypesChanged) filter.contentRating.observe(viewLifecycleOwner, this::onContentRatingChanged) @@ -103,6 +104,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), binding.chipsDemographics.onChipClickListener = this binding.chipsGenres.onChipClickListener = this binding.chipsGenresExclude.onChipClickListener = this + binding.chipsAuthor.onChipClickListener = this binding.chipsSavedFilters.onChipLongClickListener = this binding.chipsSavedFilters.onChipCloseClickListener = this binding.sliderYear.addOnChangeListener(this::onSliderValueChange) @@ -199,6 +201,11 @@ class FilterSheetFragment : BaseAdaptiveSheet(), is ContentRating -> filter.toggleContentRating(data, !chip.isChecked) is Demographic -> filter.toggleDemographic(data, !chip.isChecked) is PersistableFilter -> filter.setAdjusted(data.filter) + is String -> if (chip.isChecked) { + filter.setAuthor(null) + } else { + filter.setAuthor(data) + } null -> router.showTagsCatalogSheet(excludeMode = chip.parentView?.id == R.id.chips_genresExclude) } } @@ -312,6 +319,22 @@ class FilterSheetFragment : BaseAdaptiveSheet(), b.chipsGenresExclude.setChips(chips) } + private fun onAuthorsChanged(value: FilterProperty) { + val b = viewBinding ?: return + b.layoutAuthor.isGone = value.isEmpty() + if (value.isEmpty()) { + return + } + val chips = value.availableItems.map { author -> + ChipsView.ChipModel( + title = author, + isChecked = author in value.selectedItems, + data = author, + ) + } + b.chipsAuthor.setChips(chips) + } + private fun onStateChanged(value: FilterProperty) { val b = viewBinding ?: return b.layoutState.isGone = value.isEmpty() 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 a427b809a..42465cb43 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 @@ -169,4 +169,8 @@ class MangaSearchRepository @Inject constructor( null, )?.use { cursor -> cursor.count } ?: 0 } + + suspend fun getAuthors(source: MangaSource, limit: Int): List { + return db.getMangaDao().findAuthorsBySource(source.name, limit) + } } diff --git a/app/src/main/res/layout/sheet_filter.xml b/app/src/main/res/layout/sheet_filter.xml index 5daf9a7bb..32a5aff25 100644 --- a/app/src/main/res/layout/sheet_filter.xml +++ b/app/src/main/res/layout/sheet_filter.xml @@ -162,6 +162,24 @@ + + + + + +