Add three-tier (Safe / Mixed / NSFW) content classification for extensions - #3775
Open
Hisham-AlAhmad wants to merge 2 commits into
Open
Add three-tier (Safe / Mixed / NSFW) content classification for extensions#3775Hisham-AlAhmad wants to merge 2 commits into
Hisham-AlAhmad wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a three-tier content classification (Safe / Mixed / NSFW, plus Unspecified) for extensions and updates the domain model, decoding, filtering, and UI to respect those tiers end-to-end, replacing the previous boolean NSFW flag.
Changes:
- Replace
Extension.isNsfwwithExtension.contentWarning: ContentWarningacross models and decoding (store + legacy + installed APK metadata). - Replace the old “Show NSFW sources” boolean preference with a tri-state
ContentWarningLeveland apply it to extension/source filtering. - Update UI strings/badges/dialog copy to distinguish “Mixed” from “NSFW”.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| i18n/src/commonMain/moko-resources/base/strings.xml | Adds strings for the new content warning levels and Mixed badge/dialog text. |
| domain/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt | Replaces boolean NSFW flag with contentWarning in the core extension model. |
| domain/src/main/java/eu/kanade/tachiyomi/extension/model/ContentWarning.kt | Introduces the new ContentWarning enum and hasAdultContent helper. |
| data/src/main/java/mihon/data/extension/model/NetworkLegacyExtension.kt | Maps legacy nsfw flag into the new ContentWarning domain model. |
| data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt | Preserves store-provided content warning tiers instead of collapsing to boolean. |
| app/src/main/java/mihon/core/migration/migrations/Migrations.kt | Registers the new preference migration. |
| app/src/main/java/mihon/core/migration/migrations/ContentWarningLevelMigration.kt | Migrates the old boolean NSFW preference to the new tri-state level. |
| app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt | Reads installed extension metadata into ContentWarning during load. |
| app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt | Replaces NSFW switch with a list preference for ContentWarningLevel. |
| app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt | Shows “Mixed” badge distinct from “18+” and uses contentWarning. |
| app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt | Updates age rating row and warning dialog to be tier-aware. |
| app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt | Replaces showNsfwSource with contentWarningLevel enum preference. |
| app/src/main/java/eu/kanade/domain/source/model/ContentWarningLevel.kt | Adds the new tri-state cutoff rules for discovery vs installed items. |
| app/src/main/java/eu/kanade/domain/source/interactor/GetEnabledSources.kt | Filters enabled sources using resolved extension content warning tier. |
| app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt | Applies ContentWarningLevel filtering for installed vs available extensions. |
| app/src/main/java/eu/kanade/domain/DomainModule.kt | Updates DI wiring for the new GetEnabledSources dependency. |
| app/src/main/baselineProfiles/baseline-prof.txt | Updates baseline profile entry for renamed SourcePreferences getter. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+24
to
+30
| val oldShowNsfwSource = preferenceStore.getBoolean("show_nsfw_source", true) | ||
| if (oldShowNsfwSource.isSet()) { | ||
| sourcePreferences.contentWarningLevel.set( | ||
| if (oldShowNsfwSource.get()) ContentWarningLevel.ALL else ContentWarningLevel.SAFE_AND_MIXED, | ||
| ) | ||
| oldShowNsfwSource.delete() | ||
| } |
Comment on lines
+278
to
287
| val contentWarning = if (appInfo.metaData.getInt(METADATA_NSFW) == 1) { | ||
| ContentWarning.NSFW | ||
| } else { | ||
| when (appInfo.metaData.getInt(METADATA_CONTENT_WARNING, -1)) { | ||
| 0 -> ContentWarning.SAFE | ||
| 1 -> ContentWarning.MIXED | ||
| 2 -> ContentWarning.NSFW | ||
| else -> ContentWarning.UNSPECIFIED | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add three-tier (Safe / Mixed / NSFW) content classification for extensions
Closes #1673. Builds on top of #3747, which stopped installed NSFW extensions from being force-hidden/unloaded when the NSFW preference was off, but only ever exposed a boolean (
isNsfw). This PR replaces that boolean everywhere with the tri-state classification the extension index format already partially supports, and gives users real control over where the line sits.Background
ContentWarning(SAFE / MIXED / NSFW, plus UNSPECIFIED for repos that don't send it) already existed inNetworkExtensionStore's wire format but was immediately collapsed back into a boolean:That line is effectively the bug in #1673 — an extension that's mostly safe but carries some optional adult content (MIXED) was treated identically to one that's fully adult (NSFW). This PR keeps the tier intact end-to-end instead of flattening it.
What changed
Domain model
ContentWarningenum (domain):UNSPECIFIED / SAFE / MIXED / NSFW.Extension.isNsfw: Boolean→Extension.contentWarning: ContentWarningacrossInstalled/Available/Untrusted.Decoding (
data)NetworkExtensionStore: maps the wire enum straight through instead of collapsing it.NetworkLegacyExtension: legacynsfw: 0/1maps toNSFWorUNSPECIFIED— never guessed asMIXED, since the old format has no way to express it.ExtensionLoader: reads the installed APK'stachiyomix.contentWarningmetadata into the same enum (0/1/2 = Safe/Mixed/NSFW), falling back to the legacytachiyomi.extension.nsfwflag when absent.Preference: boolean → tri-state
SourcePreferences.showNsfwSource: Boolean→contentWarningLevel: Preference<ContentWarningLevel>, a new enum (SAFE / SAFE_AND_MIXED / ALL) surfaced as aListPreference(radio dialog) in Settings → Browse, replacing the old switch.ContentWarningLevelexposes two rules:allowsDiscovery— gates not-yet-installed extensions/sources in Browse (cumulative:SAFEshows only Safe,SAFE_AND_MIXEDalso shows Mixed,ALLshows everything).allowsInstalled— gates already-installed extensions/sources. Only the strictest level (SAFE) hides installed Mixed/NSFW;SAFE_AND_MIXEDandALLnever hide something the user already installed, preserving Keep installed NSFW extensions visible when NSFW toggle is off #3747's fix (no update-starvation).ContentWarningLevelMigration(v26): maps existing users' old boolean 1:1 to the new enum (true → ALL,false → SAFE_AND_MIXED) so nobody's effective setting changes on update.Filtering
GetExtensionsByType(Extensions tab: installed/updates/available) andGetEnabledSources(Sources tab) both now filter throughContentWarningLevel. These were two independent data paths — the Sources tab has no direct link to an extension's classification, soGetEnabledSourcesnow cross-referencesExtensionManager.installedExtensionsFlowto resolve each source's owning extension's tier.UI
MIXEDshown alongside the existing18+badge, same style/color, reusing the pattern already in place.Behavior summary