Add manga blacklist#1609
Conversation
Reviewer's GuideImplements a user-managed series blacklist backed by source preferences, wires it into browse and global search flows to filter out matching manga by normalized title, and exposes management via a new settings screen and reusable manga-actions bottom sheet, including bulk-blacklist support and basic tests. Sequence diagram for adding a manga title to the blacklist from browsesequenceDiagram
actor User
participant BrowseSourceScreen as BrowseSourceScreen
participant MangaActionsDialog as MangaActionsDialog
participant SourcePreferences as SourcePreferences
participant Toast as AndroidToast
User->>BrowseSourceScreen: Long_press_manga_item
BrowseSourceScreen->>BrowseSourceScreen: actionManga = selected_manga
BrowseSourceScreen->>MangaActionsDialog: Show(manga)
User->>MangaActionsDialog: Tap_Add_to_blacklist
MangaActionsDialog->>SourcePreferences: addBlacklistedSeries(manga.title)
alt title_added
SourcePreferences-->>MangaActionsDialog: true
MangaActionsDialog->>BrowseSourceScreen: onDismissRequest()
BrowseSourceScreen->>BrowseSourceScreen: actionManga = null
BrowseSourceScreen->>BrowseSourceScreen: blacklist_prefs_flow_emits_updated_list
else title_already_exists
SourcePreferences-->>MangaActionsDialog: false
MangaActionsDialog->>Toast: context.toast(blacklist_title_exists)
end
Note over SourcePreferences,BrowseSourceScreen: Updated blacklist is serialized in preferences and propagated via blacklistedSeries().changes() and enableSeriesBlacklist().changes() flows to BrowseSourceScreenModel, which updates state.blacklistedTitles and filters listings.
Sequence diagram for filtering browse and global search results by blacklistsequenceDiagram
participant SourcePreferences as SourcePreferences
participant BrowseSourceScreenModel as BrowseSourceScreenModel
participant SearchScreenModel as SearchScreenModel
participant PagingFlow as BrowsePagingFlow
participant GlobalSearchState as GlobalSearchState
SourcePreferences-->>BrowseSourceScreenModel: enableSeriesBlacklist().get()
SourcePreferences-->>BrowseSourceScreenModel: blacklistedSeries().get()
BrowseSourceScreenModel->>BrowseSourceScreenModel: init blacklistedTitles = normalizedTitles
SourcePreferences-->>SearchScreenModel: enableSeriesBlacklist().get()
SourcePreferences-->>SearchScreenModel: blacklistedSeries().get()
SearchScreenModel->>SearchScreenModel: init blacklistedTitles = normalizedTitles
par reactive_updates
SourcePreferences-->>BrowseSourceScreenModel: enableSeriesBlacklist().changes()
SourcePreferences-->>BrowseSourceScreenModel: blacklistedSeries().changes()
BrowseSourceScreenModel->>BrowseSourceScreenModel: combine(...) -> update state.blacklistedTitles
and
SourcePreferences-->>SearchScreenModel: enableSeriesBlacklist().changes()
SourcePreferences-->>SearchScreenModel: blacklistedSeries().changes()
SearchScreenModel->>SearchScreenModel: combine(...) -> update state.blacklistedTitles
end
BrowseSourceScreenModel->>PagingFlow: Pager(...).flow
PagingFlow-->>BrowseSourceScreenModel: PagingData~Flow~StateFlow~Pair~Manga, Metadata?~~~~
BrowseSourceScreenModel->>PagingFlow: filter { !hideInLibraryItems || !favorite }
BrowseSourceScreenModel->>PagingFlow: blacklistedTitles.flatMapLatest { titles -> if (titles.isNotEmpty()) filter { manga.title.toBlacklistNormalizedTitle() !in titles } }
SearchScreenModel->>GlobalSearchState: compute filteredItems
GlobalSearchState->>GlobalSearchState: for each Success result, filter result.result where manga.title.toBlacklistNormalizedTitle() !in blacklistedTitles
Note over PagingFlow,GlobalSearchState: Both browse paging and global search<br/>use the same normalized-title blacklist to hide matching series from results.
Class diagram for series blacklist domain and preferencesclassDiagram
class BlacklistedSeriesEntry {
<<data>>
+String originalTitle
+String normalizedTitle
}
class SourcePreferences {
-PreferenceStore preferenceStore
-ListSerializer~BlacklistedSeriesEntry~ blacklistSerializer
+Preference~List~BlacklistedSeriesEntry~~ blacklistedSeries()
+Boolean addBlacklistedSeries(title: String)
+Unit removeBlacklistedSeries(normalizedTitle: String)
+Preference~Boolean~ enableSeriesBlacklist()
-List~BlacklistedSeriesEntry~ sanitizeBlacklistedSeries(entries: List~BlacklistedSeriesEntry~)
}
class BrowseSourceScreenModelState {
+Set~String~ blacklistedTitles
}
class BrowseSourceScreenModel {
-SourcePreferences sourcePreferences
-MutableStateFlow~BrowseSourceScreenModelState~ mutableState
+StateFlow~BrowseSourceScreenModelState~ state
+Unit init
+Flow~Flow~StateFlow~Pair~Manga, RaisedSearchMetadata?~~~~ mangaPagerFlowFlow
}
class SearchScreenModelState {
+Set~String~ blacklistedTitles
+PersistentMap~CatalogueSource, SearchItemResult~ items
+PersistentMap~CatalogueSource, SearchItemResult~ filteredItems
}
class SearchScreenModel {
-SourcePreferences preferences
-MutableStateFlow~SearchScreenModelState~ mutableState
+StateFlow~SearchScreenModelState~ state
+Unit init
}
class BulkFavoriteScreenModelState {
+Set~Manga~ selection
+Boolean selectionMode
+Boolean isRunning
}
class BulkFavoriteScreenModel {
-SourcePreferences sourcePreferences
-MutableStateFlow~BulkFavoriteScreenModelState~ state
+Unit massBlacklist()
}
class StringExtensions {
+String toBlacklistNormalizedTitle()
}
SourcePreferences --> BlacklistedSeriesEntry : stores
BrowseSourceScreenModel --> SourcePreferences : reads_blacklistedSeries
BrowseSourceScreenModel --> BrowseSourceScreenModelState : updates
BrowseSourceScreenModelState --> BlacklistedSeriesEntry : holds_normalized_titles
SearchScreenModel --> SourcePreferences : reads_blacklistedSeries
SearchScreenModel --> SearchScreenModelState : updates
BulkFavoriteScreenModel --> SourcePreferences : addBlacklistedSeries
BulkFavoriteScreenModel --> BulkFavoriteScreenModelState : updates
StringExtensions <.. BlacklistedSeriesEntry : uses_to_normalize_titles
StringExtensions <.. SourcePreferences : uses_to_sanitize_entries
StringExtensions <.. BrowseSourceScreenModel : uses_to_filter_paged_results
StringExtensions <.. SearchScreenModel : uses_to_filter_search_results
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a series blacklist feature to the application, enabling users to filter out unwanted manga titles from their browsing experience. The implementation includes a new UI for managing these entries, backend logic for title normalization to ensure accurate matching, and seamless integration with existing preference management for data persistence. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. A title you wish not to see, Is hidden away, set to be free. With logic so sound, No matches are found, In browse, it will no longer be. Footnotes
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Both
BrowseSourceScreenModelandSearchScreenModelmapblacklistedSeries()to aSet<String>of normalized titles in the same way; consider extracting this into a shared helper/extension to avoid duplication and keep blacklist handling consistent. - Filtering out blacklisted manga currently normalizes each manga title on-the-fly via
toBlacklistNormalizedTitle()in both browse and global search flows; if this becomes a hotspot with large lists, consider caching the normalized title per manga or precomputing it at the boundary to avoid repeated normalization work.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Both `BrowseSourceScreenModel` and `SearchScreenModel` map `blacklistedSeries()` to a `Set<String>` of normalized titles in the same way; consider extracting this into a shared helper/extension to avoid duplication and keep blacklist handling consistent.
- Filtering out blacklisted manga currently normalizes each manga title on-the-fly via `toBlacklistNormalizedTitle()` in both browse and global search flows; if this becomes a hotspot with large lists, consider caching the normalized title per manga or precomputing it at the boundary to avoid repeated normalization work.
## Individual Comments
### Comment 1
<location path="app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt" line_range="73-81" />
<code_context>
+ },
+ )
+
+ fun addBlacklistedSeries(title: String): Boolean {
+ val originalTitle = title.trim()
+ val normalizedTitle = originalTitle.toBlacklistNormalizedTitle()
+ if (originalTitle.isBlank() || normalizedTitle.isBlank()) return false
+
+ val entries = blacklistedSeries().get()
+ if (entries.any { it.normalizedTitle == normalizedTitle }) return false
+
+ blacklistedSeries().set(
+ entries + BlacklistedSeriesEntry(
+ originalTitle = originalTitle,
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider using a read-modify-write helper to avoid races when updating the blacklist preference.
`addBlacklistedSeries` does a `get()` followed by `set(entries + ...)`, which can drop updates if multiple callers run concurrently (the last `set` wins). If the `Preference` API supports an atomic `update`/`transform` (e.g. `blacklistedSeries().update { ... }`), prefer that to perform the read-modify-write in one step and keep the deduplication aligned with the sanitizer.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request introduces a series blacklist feature, allowing users to hide specific titles from browse and search results. It includes a new management screen, preference settings, and title normalization logic to ensure consistent filtering. Feedback focuses on improving state persistence during configuration changes, optimizing the Paging flow to prevent unnecessary reloads when the blacklist updates, and refining UI details such as handling long titles and avoiding hardcoded font sizes.
|
Can wild cards be used? |
|
This is something, thanks. |
No, current PR works on exact titles. Adding regex support is possible but it will make UX much more complex and hard to explain for newcomers without technical skills. |
|
@sourcery-ai summary |
|
@sourcery-ai guide |
|
@cuong-tran any chance of this PR being merged anytime soon? |
|
Sorry for the long waiting, I will look at the pending PRs soon. |
|
merged upstream; added missing KMK markers (no real code changes) |
My two unsolicited cents and a biased opinion: regex/wildcards in advanced settings should work, as it doesn't mess with UX and improves speed of lookup for heavy users, since one regex rule would be way less demanding than 2k blocklisted titles, generally speaking. For those that handle many extensions and like 5k titles (such as myself), it would be godsend. |
well, more demanding in terms of performance. current implementation does simple inclusion in a set which is plenty performant. with 2k blocklist regex lines it would be a slow check line by line for every manga title. Also, regex is not compatible with current flow - block directly in the list by selecting some manga and clicking "block" It cloud make sense to add regex in addition to that this PR already does, assuming it will contain small amount of hand-crafted rules made by advanced users. I don't want to increase complexity too much though. |










This PR adds ability to hide series you don't like from "Browse" section of the app.
Hiding works by title only and does not depend on any extension.
The UI to add/remove titles located at Settings -> Browse -> Series blacklist
Manga can be hidden via context menu (see screenshots)
Blacklist setting are checked to be backed up/restored properly
Summary by Sourcery
Introduce a title-based series blacklist that hides selected manga from browse and search results and can be managed from settings or item actions.
New Features:
Enhancements:
Tests: