From 0fbaa1ecece412808dfd2bb1ef4ce963bcf8f937 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:00:06 +0600 Subject: [PATCH] Replace extension NSFW flag with content warning app-wide (#3951) --- app/build.gradle.kts | 2 +- .../interactor/GetExtensionsByType.kt | 5 +- .../source/service/SourcePreferences.kt | 7 +- .../browse/ExtensionDetailsScreen.kt | 68 ++++++++++++------- .../presentation/browse/ExtensionsScreen.kt | 24 ++++--- .../browse/components/ContentWarningLabel.kt | 32 +++++++++ .../settings/screen/SettingsBrowseScreen.kt | 37 +++++++--- .../extension/util/ExtensionLoader.kt | 22 ++++-- .../migrations/ContentWarningMigration.kt | 31 +++++++++ .../extension/model/NetworkExtensionStore.kt | 8 ++- .../extension/model/NetworkLegacyExtension.kt | 3 +- .../tachiyomi/extension/model/Extension.kt | 9 +-- .../domain/extension/model/ContentWarning.kt | 7 ++ .../moko-resources/base/strings.xml | 15 ++-- 14 files changed, 202 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/browse/components/ContentWarningLabel.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ContentWarningMigration.kt create mode 100644 domain/src/main/java/mihon/domain/extension/model/ContentWarning.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 734f415c7..380c4a708 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,7 +33,7 @@ android { defaultConfig { applicationId = "app.mihon" - versionCode = 29 + versionCode = 30 versionName = "0.20.4" buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"") diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt index ff305cf09..56624ee87 100644 --- a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt @@ -15,7 +15,7 @@ class GetExtensionsByType( ) { fun subscribe(): Flow { - val showNsfwSources = preferences.showNsfwSource.get() + val enabledContentWarnings = preferences.enabledContentWarnings.get() return combine( preferences.enabledLanguages.changes(), @@ -24,7 +24,6 @@ class GetExtensionsByType( extensionManager.availableExtensionsFlow, ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed - .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( compareBy { !it.isObsolete } .thenBy(String.CASE_INSENSITIVE_ORDER) { it.name }, @@ -38,7 +37,7 @@ class GetExtensionsByType( .filter { extension -> _installed.none { it.pkgName == extension.pkgName } && _untrusted.none { it.pkgName == extension.pkgName } && - (showNsfwSources || !extension.isNsfw) + extension.contentWarning in enabledContentWarnings } .flatMap { ext -> ext.sources.filter { it.lang in enabledLanguages } diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 80ad1ff88..39c64eb02 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -5,10 +5,12 @@ import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.tachiyomi.util.system.LocaleHelper +import mihon.domain.extension.model.ContentWarning import mihon.domain.migration.models.MigrationFlag import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.getEnum +import tachiyomi.core.common.preference.getEnumSet import tachiyomi.core.common.preference.getLongArray import tachiyomi.domain.library.model.LibraryDisplayMode @@ -41,7 +43,10 @@ class SourcePreferences( -1, ) - val showNsfwSource: Preference = preferenceStore.getBoolean("show_nsfw_source", true) + val enabledContentWarnings: Preference> = preferenceStore.getEnumSet( + "enabled_content_warnings", + setOf(ContentWarning.SAFE, ContentWarning.MIXED, ContentWarning.NSFW), + ) val migrationSortingMode: Preference = preferenceStore.getEnum( "pref_migration_sorting", diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt index 7c446a3b3..2f58348e1 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt @@ -32,16 +32,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import dev.icerock.moko.resources.StringResource import eu.kanade.domain.extension.interactor.ExtensionSourceItem import eu.kanade.presentation.browse.components.ExtensionIcon +import eu.kanade.presentation.browse.components.label import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.WarningBanner @@ -152,7 +154,8 @@ private fun ExtensionDetails( onClickIncognito: (Boolean) -> Unit, ) { val context = LocalContext.current - var showNsfwWarning by remember { mutableStateOf(false) } + var showContentWarning by remember { mutableStateOf(false) } + val contentWarning = extension.contentWarning.label ScrollbarLazyColumn( contentPadding = contentPadding, @@ -175,8 +178,8 @@ private fun ExtensionDetails( } Unit }.takeIf { extension.isShared }, - onClickAgeRating = { - showNsfwWarning = true + onClickContentWarning = { + showContentWarning = true }, onExtIncognitoChange = onClickIncognito, ) @@ -194,10 +197,12 @@ private fun ExtensionDetails( ) } } - if (showNsfwWarning) { - NsfwWarningDialog( + if (showContentWarning && contentWarning != null) { + ContentWarningDialog( + label = contentWarning.title, + description = contentWarning.description, onClickConfirm = { - showNsfwWarning = false + showContentWarning = false }, ) } @@ -207,12 +212,13 @@ private fun ExtensionDetails( private fun DetailsHeader( extension: Extension, extIncognitoMode: Boolean, - onClickAgeRating: () -> Unit, + onClickContentWarning: () -> Unit, onClickUninstall: () -> Unit, onClickAppInfo: (() -> Unit)?, onExtIncognitoChange: (Boolean) -> Unit, ) { val context = LocalContext.current + val contentWarning = extension.contentWarning.label Column { Column( @@ -229,7 +235,7 @@ private fun DetailsHeader( """ Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName}) Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode}) - NSFW: ${extension.isNsfw} + Content warning: ${extension.contentWarning} """.trimIndent(), ) @@ -277,7 +283,7 @@ private fun DetailsHeader( modifier = Modifier .fillMaxWidth() .padding( - horizontal = MaterialTheme.padding.extraLarge, + horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small, ), horizontalArrangement = Arrangement.SpaceEvenly, @@ -292,23 +298,20 @@ private fun DetailsHeader( InfoDivider() InfoText( - modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f), + modifier = Modifier.weight(1f), primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context), secondaryText = stringResource(MR.strings.ext_info_language), ) - if (extension.isNsfw) { + if (contentWarning != null) { InfoDivider() InfoText( modifier = Modifier.weight(1f), - primaryText = stringResource(MR.strings.ext_nsfw_short), - primaryTextStyle = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.error, - fontWeight = FontWeight.Medium, - ), - secondaryText = stringResource(MR.strings.ext_info_age_rating), - onClick = onClickAgeRating, + primaryText = stringResource(contentWarning.title), + primaryTextColor = contentWarning.color, + secondaryText = stringResource(MR.strings.ext_info_warning), + onClick = onClickContentWarning, ) } } @@ -366,7 +369,7 @@ private fun InfoText( primaryText: String, secondaryText: String, modifier: Modifier = Modifier, - primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge, + primaryTextColor: Color = Color.Unspecified, onClick: (() -> Unit)? = null, ) { val clickableModifier = if (onClick != null) { @@ -383,13 +386,16 @@ private fun InfoText( Text( text = primaryText, textAlign = TextAlign.Center, - style = primaryTextStyle, + style = MaterialTheme.typography.titleSmall, + color = primaryTextColor, + maxLines = 2, + overflow = TextOverflow.Ellipsis, ) Text( text = secondaryText + if (onClick != null) " ⓘ" else "", textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), ) } @@ -398,7 +404,9 @@ private fun InfoText( @Composable private fun InfoDivider() { VerticalDivider( - modifier = Modifier.height(20.dp), + modifier = Modifier + .padding(horizontal = MaterialTheme.padding.small) + .height(24.dp), ) } @@ -444,12 +452,20 @@ private fun SourceSwitchPreference( } @Composable -private fun NsfwWarningDialog( +private fun ContentWarningDialog( + label: StringResource, + description: StringResource, onClickConfirm: () -> Unit, ) { AlertDialog( + title = { + Text( + text = stringResource(label), + style = MaterialTheme.typography.titleMedium, + ) + }, text = { - Text(text = stringResource(MR.strings.ext_nsfw_warning)) + Text(text = stringResource(description)) }, confirmButton = { TextButton(onClick = onClickConfirm) { diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt index 9a1811c00..904e52f45 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt @@ -38,6 +38,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow import dev.icerock.moko.resources.StringResource import eu.kanade.presentation.browse.components.BaseBrowseItem import eu.kanade.presentation.browse.components.ExtensionIcon +import eu.kanade.presentation.browse.components.label import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen @@ -367,22 +368,27 @@ private fun ExtensionItemContent( ) } - val warning = when { - extension is Extension.Untrusted -> MR.strings.ext_untrusted - extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete - extension.isNsfw -> MR.strings.ext_nsfw_short - else -> null - } - if (warning != null) { + val warnings = listOfNotNull( + when { + extension is Extension.Untrusted -> + MR.strings.ext_untrusted to MaterialTheme.colorScheme.error + extension is Extension.Installed && extension.isObsolete -> + MR.strings.ext_obsolete to MaterialTheme.colorScheme.error + else -> null + }, + extension.contentWarning.label?.let { it.title to it.color }, + ) + warnings.forEach { (label, color) -> if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText() hasAlreadyShownAnElement = true Text( - text = stringResource(warning).uppercase(), - color = MaterialTheme.colorScheme.error, + text = stringResource(label).uppercase(), + color = color, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } + if (extension is Extension.Installed && !extension.isShared) { if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText() Text( diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/ContentWarningLabel.kt b/app/src/main/java/eu/kanade/presentation/browse/components/ContentWarningLabel.kt new file mode 100644 index 000000000..86d4f4ace --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/browse/components/ContentWarningLabel.kt @@ -0,0 +1,32 @@ +package eu.kanade.presentation.browse.components + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color +import dev.icerock.moko.resources.StringResource +import mihon.domain.extension.model.ContentWarning +import tachiyomi.i18n.MR + +internal data class ContentWarningLabel( + val title: StringResource, + val description: StringResource, + val color: Color, +) + +internal val ContentWarning.label: ContentWarningLabel? + @Composable + @ReadOnlyComposable + get() = when (this) { + ContentWarning.SAFE -> null + ContentWarning.MIXED -> ContentWarningLabel( + title = MR.strings.ext_content_warning_mixed, + description = MR.strings.ext_content_warning_mixed_description, + color = MaterialTheme.colorScheme.primary, + ) + ContentWarning.NSFW -> ContentWarningLabel( + title = MR.strings.ext_content_warning_nsfw, + description = MR.strings.ext_content_warning_nsfw_description, + color = MaterialTheme.colorScheme.error, + ) + } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 3c0895a68..7d88d827b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -13,6 +13,7 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import mihon.app.di.appGraph +import mihon.domain.extension.model.ContentWarning import tachiyomi.core.common.i18n.stringResource import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.pluralStringResource @@ -52,19 +53,35 @@ object SettingsBrowseScreen : SearchableSettings { ), ), Preference.PreferenceGroup( - title = stringResource(MR.strings.pref_category_nsfw_content), + title = stringResource(MR.strings.pref_category_extensions), preferenceItems = listOf( - Preference.PreferenceItem.SwitchPreference( - preference = sourcePreferences.showNsfwSource, - title = stringResource(MR.strings.pref_show_nsfw_source), - subtitle = stringResource(MR.strings.requires_app_restart), - onValueChanged = { - (context as FragmentActivity).authenticate( - title = context.stringResource(MR.strings.pref_category_nsfw_content), - ) + Preference.PreferenceItem.MultiSelectListPreference( + preference = sourcePreferences.enabledContentWarnings, + entries = mapOf( + ContentWarning.SAFE to stringResource(MR.strings.ext_content_warning_safe), + ContentWarning.MIXED to stringResource(MR.strings.ext_content_warning_mixed), + ContentWarning.NSFW to stringResource(MR.strings.ext_content_warning_nsfw), + ), + title = stringResource(MR.strings.pref_allowed_content_warnings), + subtitleProvider = { value, entries -> + remember(value, entries) { + entries.filterKeys { it in value }.values.joinToString() + } + .takeUnless { it.isBlank() } + ?: stringResource(MR.strings.none) + }, + onValueChanged = { newValue -> + val added = newValue - sourcePreferences.enabledContentWarnings.get() + if (added.any { it != ContentWarning.SAFE }) { + (context as FragmentActivity).authenticate( + title = context.stringResource(MR.strings.pref_allowed_content_warnings), + ) + } else { + true + } }, ), - Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)), + Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.content_warnings_info)), ), ), ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index c3faa7f7f..e2a0552d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.runBlocking import logcat.LogPriority import mihon.app.di.appGraph import mihon.data.dalvik.DelegateLastClassLoaderCompat +import mihon.domain.extension.model.ContentWarning import tachiyomi.core.common.util.system.logcat import java.io.File @@ -222,7 +223,7 @@ internal object ExtensionLoader { */ private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult { val trustExtension: TrustExtension = context.appGraph.trustExtension - val loadNsfwSource: Boolean = context.appGraph.sourcePreferences.showNsfwSource.get() + val enabledContentWarnings = context.appGraph.sourcePreferences.enabledContentWarnings.get() val pkgManager = context.packageManager val pkgInfo = extensionInfo.packageInfo @@ -269,10 +270,19 @@ internal object ExtensionLoader { return LoadResult.Untrusted(extension) } - val isNsfw = appInfo.metaData.getInt(METADATA_CONTENT_WARNING) > 0 || - appInfo.metaData.getInt(METADATA_NSFW) == 1 - if (!loadNsfwSource && isNsfw) { - logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" } + val contentWarning = when { + appInfo.metaData.containsKey(METADATA_CONTENT_WARNING) -> { + when (appInfo.metaData.getInt(METADATA_CONTENT_WARNING)) { + 1 -> ContentWarning.MIXED + 2 -> ContentWarning.NSFW + else -> ContentWarning.SAFE + } + } + appInfo.metaData.getInt(METADATA_NSFW) == 1 -> ContentWarning.NSFW + else -> ContentWarning.SAFE + } + if (contentWarning !in enabledContentWarnings) { + logcat(LogPriority.WARN) { "Extension $pkgName with $contentWarning not allowed" } return LoadResult.Error } @@ -320,7 +330,7 @@ internal object ExtensionLoader { versionCode = versionCode, libVersion = libVersion, lang = lang, - isNsfw = isNsfw, + contentWarning = contentWarning, sources = sources, pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY), icon = appInfo.loadIcon(pkgManager), diff --git a/app/src/main/java/mihon/core/migration/migrations/ContentWarningMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ContentWarningMigration.kt new file mode 100644 index 000000000..5b304d133 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ContentWarningMigration.kt @@ -0,0 +1,31 @@ +package mihon.core.migration.migrations + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesIntoSet +import dev.zacsweers.metro.Inject +import eu.kanade.domain.source.service.SourcePreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import mihon.domain.extension.model.ContentWarning +import tachiyomi.core.common.preference.PreferenceStore + +@Inject +@ContributesIntoSet(AppScope::class) +class ContentWarningMigration( + private val preferenceStore: PreferenceStore, + private val sourcePreferences: SourcePreferences, +) : Migration { + override val version: Float = 30f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val showNsfwSource = preferenceStore.getBoolean("show_nsfw_source", true) + if (!showNsfwSource.isSet()) return true + + if (!showNsfwSource.get()) { + sourcePreferences.enabledContentWarnings.set(setOf(ContentWarning.SAFE)) + } + showNsfwSource.delete() + + return true + } +} diff --git a/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt b/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt index 756dc5c48..75e413db1 100644 --- a/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt +++ b/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt @@ -8,6 +8,7 @@ import mihon.data.extension.model.NetworkExtensionStore.ContentWarning import mihon.data.extension.model.NetworkExtensionStore.ExtensionList import mihon.domain.extension.model.ExtensionStore import eu.kanade.tachiyomi.extension.model.Extension as TachiyomiExtension +import mihon.domain.extension.model.ContentWarning as DomainContentWarning @SuppressLint("UnsafeOptInUsageError") @Serializable @@ -104,7 +105,12 @@ fun ExtensionList.toAvailableExtensions(store: ExtensionStore): List= ContentWarning.MIXED, + contentWarning = when (extension.contentWarning) { + ContentWarning.SAFE -> DomainContentWarning.SAFE + ContentWarning.MIXED -> DomainContentWarning.MIXED + ContentWarning.NSFW -> DomainContentWarning.NSFW + else -> DomainContentWarning.SAFE + }, sources = extension.sources.map { source -> TachiyomiExtension.Available.Source( id = source.id, diff --git a/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtension.kt b/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtension.kt index 9a47907ce..ecf306e15 100644 --- a/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtension.kt +++ b/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtension.kt @@ -3,6 +3,7 @@ package mihon.data.extension.model import android.annotation.SuppressLint import eu.kanade.tachiyomi.extension.model.Extension import kotlinx.serialization.Serializable +import mihon.domain.extension.model.ContentWarning import mihon.domain.extension.model.ExtensionStore @SuppressLint("UnsafeOptInUsageError") @@ -35,7 +36,7 @@ data class NetworkLegacyExtension( versionCode = code, versionName = version, lang = lang, - isNsfw = nsfw == 1, + contentWarning = if (nsfw == 1) ContentWarning.NSFW else ContentWarning.SAFE, sources = if (sources.isNullOrEmpty()) { listOf( Extension.Available.Source( diff --git a/domain/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt b/domain/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt index 2d6d2041e..a057ec1b3 100644 --- a/domain/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt +++ b/domain/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.model import android.graphics.drawable.Drawable import eu.kanade.tachiyomi.source.Source +import mihon.domain.extension.model.ContentWarning import mihon.domain.extension.model.ExtensionStore import tachiyomi.domain.source.model.StubSource @@ -13,7 +14,7 @@ sealed class Extension { abstract val versionCode: Long abstract val libVersion: Double abstract val lang: String? - abstract val isNsfw: Boolean + abstract val contentWarning: ContentWarning data class Installed( override val name: String, @@ -22,7 +23,7 @@ sealed class Extension { override val versionCode: Long, override val libVersion: Double, override val lang: String, - override val isNsfw: Boolean, + override val contentWarning: ContentWarning, val pkgFactory: String?, val sources: List, val icon: Drawable?, @@ -39,7 +40,7 @@ sealed class Extension { override val versionCode: Long, override val libVersion: Double, override val lang: String, - override val isNsfw: Boolean, + override val contentWarning: ContentWarning, val sources: List, val apkUrl: String, val iconUrl: String, @@ -70,6 +71,6 @@ sealed class Extension { override val libVersion: Double, val signatureHash: String, override val lang: String? = null, - override val isNsfw: Boolean = false, + override val contentWarning: ContentWarning = ContentWarning.SAFE, ) : Extension() } diff --git a/domain/src/main/java/mihon/domain/extension/model/ContentWarning.kt b/domain/src/main/java/mihon/domain/extension/model/ContentWarning.kt new file mode 100644 index 000000000..2f4a68995 --- /dev/null +++ b/domain/src/main/java/mihon/domain/extension/model/ContentWarning.kt @@ -0,0 +1,7 @@ +package mihon.domain.extension.model + +enum class ContentWarning { + SAFE, + MIXED, + NSFW, +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 83dbc65dd..b55589c09 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -266,9 +266,9 @@ Secure screen hides app contents when switching apps and block screenshots Sending crash logs and analytics will allow us to identify and fix issues, improve performance, and make future updates more relevant to your needs - NSFW (18+) sources - Show in sources and extensions lists - This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app. + Extensions + Allowed content warnings + Requires app restart to take effect. Extensions that aren\'t loaded won\'t provide their sources anywhere in the app. This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing 18+ content within the app. Today @@ -349,9 +349,12 @@ Failed to fetch available extensions Version Language - Age rating - 18+ - Sources from this extension may contain NSFW (18+) content + Warning + Safe + Mixed + 18+ + Sources from this extension might contain both safe and 18+ content. + Sources from this extension are primarily focused on 18+ content. Permissions are needed to install extensions. Tap here to grant. Installing extension… Installer