Skip to content

Add three-tier (Safe / Mixed / NSFW) content classification for extensions - #3775

Open
Hisham-AlAhmad wants to merge 2 commits into
mihonapp:mainfrom
Hisham-AlAhmad:feat/nsfw-content-tiers
Open

Add three-tier (Safe / Mixed / NSFW) content classification for extensions#3775
Hisham-AlAhmad wants to merge 2 commits into
mihonapp:mainfrom
Hisham-AlAhmad:feat/nsfw-content-tiers

Conversation

@Hisham-AlAhmad

Copy link
Copy Markdown

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 in NetworkExtensionStore's wire format but was immediately collapsed back into a boolean:

isNsfw = extension.contentWarning >= ContentWarning.MIXED

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

  • New ContentWarning enum (domain): UNSPECIFIED / SAFE / MIXED / NSFW.
  • Extension.isNsfw: BooleanExtension.contentWarning: ContentWarning across Installed / Available / Untrusted.

Decoding (data)

  • NetworkExtensionStore: maps the wire enum straight through instead of collapsing it.
  • NetworkLegacyExtension: legacy nsfw: 0/1 maps to NSFW or UNSPECIFIED — never guessed as MIXED, since the old format has no way to express it.
  • ExtensionLoader: reads the installed APK's tachiyomix.contentWarning metadata into the same enum (0/1/2 = Safe/Mixed/NSFW), falling back to the legacy tachiyomi.extension.nsfw flag when absent.

Preference: boolean → tri-state

  • SourcePreferences.showNsfwSource: BooleancontentWarningLevel: Preference<ContentWarningLevel>, a new enum (SAFE / SAFE_AND_MIXED / ALL) surfaced as a ListPreference (radio dialog) in Settings → Browse, replacing the old switch.
  • ContentWarningLevel exposes two rules:
    • allowsDiscovery — gates not-yet-installed extensions/sources in Browse (cumulative: SAFE shows only Safe, SAFE_AND_MIXED also shows Mixed, ALL shows everything).
    • allowsInstalled — gates already-installed extensions/sources. Only the strictest level (SAFE) hides installed Mixed/NSFW; SAFE_AND_MIXED and ALL never hide something the user already installed, preserving Keep installed NSFW extensions visible when NSFW toggle is off #3747's fix (no update-starvation).
  • Added 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) and GetEnabledSources (Sources tab) both now filter through ContentWarningLevel. These were two independent data paths — the Sources tab has no direct link to an extension's classification, so GetEnabledSources now cross-references ExtensionManager.installedExtensionsFlow to resolve each source's owning extension's tier.

UI

  • Extension list badge: MIXED shown alongside the existing 18+ badge, same style/color, reusing the pattern already in place.
  • Extension details screen: age-rating row and warning dialog now read tier-appropriately ("mostly safe, but some content is NSFW" for Mixed vs the existing NSFW copy).

Behavior summary

Safe only Safe & Mixed (default) All
SAFE, any visible visible visible
MIXED, not installed hidden visible + badge visible + badge
MIXED, installed hidden visible + badge visible + badge
NSFW, not installed hidden hidden visible + badge
NSFW, installed hidden visible + badge (per #3747) visible + badge

Copilot AI lite review requested due to automatic review settings August 15, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.isNsfw with Extension.contentWarning: ContentWarning across models and decoding (store + legacy + installed APK metadata).
  • Replace the old “Show NSFW sources” boolean preference with a tri-state ContentWarningLevel and 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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installed NSFW Extensions Should Remain Accessable & Receive Updates Even if NSFW Visibility is Off

2 participants