mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2025-01-23 13:11:14 +01:00
Adding NSFW warning to extensions
Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
parent
17a1f49c2d
commit
e8cba5c164
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Nsfw
|
@ -159,6 +159,10 @@ object PreferenceKeys {
|
||||
|
||||
const val enableDoh = "enable_doh"
|
||||
|
||||
const val showNsfwSource = "show_nsfw_source"
|
||||
const val showNsfwExtension = "show_nsfw_extension"
|
||||
const val labelNsfwExtension = "label_nsfw_extension"
|
||||
|
||||
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
|
||||
|
||||
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||
|
@ -301,6 +301,10 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun hideBottomNavOnScroll() = flowPrefs.getBoolean(Keys.hideBottomNavOnScroll, true)
|
||||
|
||||
fun showNsfwSource() = flowPrefs.getBoolean(Keys.showNsfwSource, true)
|
||||
fun showNsfwExtension() = flowPrefs.getBoolean(Keys.showNsfwExtension, true)
|
||||
fun labelNsfwExtension() = prefs.getBoolean(Keys.labelNsfwExtension, true)
|
||||
|
||||
fun createLegacyBackup() = flowPrefs.getBoolean(Keys.createLegacyBackup, true)
|
||||
fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false)
|
||||
}
|
||||
|
@ -1,90 +1,86 @@
|
||||
package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import android.content.Context
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.get
|
||||
import com.github.salomonbrys.kotson.int
|
||||
import com.github.salomonbrys.kotson.string
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Response
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class ExtensionGithubApi {
|
||||
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
suspend fun findExtensions(): List<Extension.Available> {
|
||||
val call = GET("$REPO_URL/index.json")
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
parseResponse(network.client.newCall(call).await())
|
||||
network.client
|
||||
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
|
||||
.await()
|
||||
.parseAs<JsonArray>()
|
||||
.let { parseResponse(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkForUpdates(context: Context): List<Extension.Installed> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val call = GET("$REPO_URL/index.json")
|
||||
val response = network.client.newCall(call).await()
|
||||
val extensions = findExtensions()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val extensions = parseResponse(response)
|
||||
val extensionsWithUpdate = mutableListOf<Extension.Installed>()
|
||||
val installedExtensions = ExtensionLoader.loadExtensions(context)
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
.map { it.extension }
|
||||
|
||||
val installedExtensions =
|
||||
ExtensionLoader.loadExtensions(context).filterIsInstance<LoadResult.Success>()
|
||||
.map { it.extension }
|
||||
val mutInstalledExtensions = installedExtensions.toMutableList()
|
||||
for (installedExt in mutInstalledExtensions) {
|
||||
val pkgName = installedExt.pkgName
|
||||
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
|
||||
val extensionsWithUpdate = mutableListOf<Extension.Installed>()
|
||||
val mutInstalledExtensions = installedExtensions.toMutableList()
|
||||
for (installedExt in mutInstalledExtensions) {
|
||||
val pkgName = installedExt.pkgName
|
||||
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
|
||||
|
||||
val hasUpdate = availableExt.versionCode > installedExt.versionCode
|
||||
if (hasUpdate) extensionsWithUpdate.add(installedExt)
|
||||
val hasUpdate = availableExt.versionCode > installedExt.versionCode
|
||||
if (hasUpdate) {
|
||||
extensionsWithUpdate.add(installedExt)
|
||||
}
|
||||
|
||||
extensionsWithUpdate
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Failed to get extensions")
|
||||
}
|
||||
|
||||
extensionsWithUpdate
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseResponse(response: Response): List<Extension.Available> {
|
||||
val text = response.body?.use { it.string() } ?: return emptyList()
|
||||
private fun parseResponse(json: kotlinx.serialization.json.JsonArray): List<Extension.Available> {
|
||||
return json
|
||||
.filter { element ->
|
||||
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
||||
val libVersion = versionName.substringBeforeLast('.').toDouble()
|
||||
libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX
|
||||
}
|
||||
.map { element ->
|
||||
val name = element.jsonObject["name"]!!.jsonPrimitive.content.substringAfter("Tachiyomi: ")
|
||||
val pkgName = element.jsonObject["pkg"]!!.jsonPrimitive.content
|
||||
val apkName = element.jsonObject["apk"]!!.jsonPrimitive.content
|
||||
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
||||
val versionCode = element.jsonObject["code"]!!.jsonPrimitive.int
|
||||
val lang = element.jsonObject["lang"]!!.jsonPrimitive.content
|
||||
val nsfw = element.jsonObject["nsfw"]!!.jsonPrimitive.int == 1
|
||||
val icon = "${REPO_URL_PREFIX}icon/${apkName.replace(".apk", ".png")}"
|
||||
|
||||
val json = gson.fromJson<JsonArray>(text)
|
||||
|
||||
return json.map { element ->
|
||||
val name = element["name"].string.substringAfter("Tachiyomi: ")
|
||||
val pkgName = element["pkg"].string
|
||||
val apkName = element["apk"].string
|
||||
val versionName = element["version"].string
|
||||
val versionCode = element["code"].int
|
||||
val lang = element["lang"].string
|
||||
val icon = "$REPO_URL/icon/${apkName.replace(".apk", ".png")}"
|
||||
|
||||
Extension.Available(name, pkgName, versionName, versionCode, lang, apkName, icon)
|
||||
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
||||
}
|
||||
}
|
||||
|
||||
fun getApkUrl(extension: Extension.Available): String {
|
||||
return "$REPO_URL/apk/${extension.apkName}"
|
||||
return "${REPO_URL_PREFIX}apk/${extension.apkName}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REPO_URL =
|
||||
"https://raw.githubusercontent.com/inorichi/tachiyomi-extensions/repo"
|
||||
private const val BASE_URL = "https://raw.githubusercontent.com/"
|
||||
const val REPO_URL_PREFIX = "${BASE_URL}tachiyomiorg/tachiyomi-extensions/repo/"
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,20 @@ sealed class Extension {
|
||||
abstract val versionName: String
|
||||
abstract val versionCode: Int
|
||||
abstract val lang: String?
|
||||
abstract val isNsfw: Boolean
|
||||
|
||||
data class Installed(
|
||||
override val name: String,
|
||||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
val sources: List<Source>,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val pkgFactory: String?,
|
||||
val sources: List<Source>,
|
||||
val hasUpdate: Boolean = false,
|
||||
val isObsolete: Boolean = false
|
||||
val isObsolete: Boolean = false,
|
||||
val isUnofficial: Boolean = false
|
||||
) : Extension()
|
||||
|
||||
data class Available(
|
||||
@ -27,6 +31,7 @@ sealed class Extension {
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val apkName: String,
|
||||
val iconUrl: String
|
||||
) : Extension()
|
||||
@ -37,6 +42,7 @@ sealed class Extension {
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
val signatureHash: String,
|
||||
override val lang: String? = null
|
||||
override val lang: String? = null,
|
||||
override val isNsfw: Boolean = false
|
||||
) : Extension()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import dalvik.system.PathClassLoader
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
@ -16,8 +17,7 @@ import eu.kanade.tachiyomi.util.lang.Hash
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Class that handles the loading of the extensions installed in the system.
|
||||
@ -25,20 +25,27 @@ import uy.kohesive.injekt.api.get
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
internal object ExtensionLoader {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
private val loadNsfwSource by lazy {
|
||||
preferences.showNsfwSource().get()
|
||||
}
|
||||
|
||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
||||
private const val LIB_VERSION_MIN = 1
|
||||
private const val LIB_VERSION_MAX = 1
|
||||
private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
|
||||
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
||||
const val LIB_VERSION_MIN = 1.2
|
||||
const val LIB_VERSION_MAX = 1.2
|
||||
|
||||
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
||||
|
||||
// inorichi's key
|
||||
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
|
||||
/**
|
||||
* List of the trusted signatures.
|
||||
*/
|
||||
var trustedSignatures = mutableSetOf<String>() +
|
||||
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
|
||||
// inorichi's key
|
||||
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().getOrDefault() + officialSignature
|
||||
|
||||
/**
|
||||
* Return a list of all the installed extensions initialized concurrently.
|
||||
@ -95,16 +102,21 @@ internal object ExtensionLoader {
|
||||
return LoadResult.Error(error)
|
||||
}
|
||||
|
||||
val extName = pkgManager.getApplicationLabel(appInfo)?.toString()
|
||||
.orEmpty().substringAfter("Tachiyomi: ")
|
||||
val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
|
||||
val versionName = pkgInfo.versionName
|
||||
val versionCode = pkgInfo.versionCode
|
||||
|
||||
if (versionName.isNullOrEmpty()) {
|
||||
val exception = Exception("Missing versionName for extension $extName")
|
||||
Timber.w(exception)
|
||||
return LoadResult.Error(exception)
|
||||
}
|
||||
|
||||
// Validate lib version
|
||||
val majorLibVersion = versionName.substringBefore('.').toInt()
|
||||
if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) {
|
||||
val libVersion = versionName.substringBeforeLast('.').toDouble()
|
||||
if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) {
|
||||
val exception = Exception(
|
||||
"Lib version is $majorLibVersion, while only versions " +
|
||||
"Lib version is $libVersion, while only versions " +
|
||||
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
|
||||
)
|
||||
Timber.w(exception)
|
||||
@ -121,6 +133,11 @@ internal object ExtensionLoader {
|
||||
return LoadResult.Untrusted(extension)
|
||||
}
|
||||
|
||||
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
||||
if (!loadNsfwSource && isNsfw) {
|
||||
return LoadResult.Error("NSFW extension $pkgName not allowed")
|
||||
}
|
||||
|
||||
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
||||
|
||||
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
||||
@ -134,10 +151,15 @@ internal object ExtensionLoader {
|
||||
}
|
||||
.flatMap {
|
||||
try {
|
||||
val obj = Class.forName(it, false, classLoader).newInstance()
|
||||
when (obj) {
|
||||
when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
||||
is Source -> listOf(obj)
|
||||
is SourceFactory -> obj.createSources()
|
||||
is SourceFactory -> {
|
||||
if (isSourceNsfw(obj)) {
|
||||
emptyList()
|
||||
} else {
|
||||
obj.createSources()
|
||||
}
|
||||
}
|
||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@ -145,6 +167,7 @@ internal object ExtensionLoader {
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
val langs = sources.filterIsInstance<CatalogueSource>()
|
||||
.map { it.lang }
|
||||
.toSet()
|
||||
@ -155,7 +178,17 @@ internal object ExtensionLoader {
|
||||
else -> "all"
|
||||
}
|
||||
|
||||
val extension = Extension.Installed(extName, pkgName, versionName, versionCode, sources, lang)
|
||||
val extension = Extension.Installed(
|
||||
extName,
|
||||
pkgName,
|
||||
versionName,
|
||||
versionCode,
|
||||
lang,
|
||||
isNsfw,
|
||||
sources = sources,
|
||||
pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY),
|
||||
isUnofficial = signatureHash != officialSignature
|
||||
)
|
||||
return LoadResult.Success(extension)
|
||||
}
|
||||
|
||||
@ -181,4 +214,22 @@ internal object ExtensionLoader {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
||||
*/
|
||||
private fun isSourceNsfw(clazz: Any): Boolean {
|
||||
if (loadNsfwSource) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (clazz !is Source && clazz !is SourceFactory) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Annotations are proxied, hence this janky way of checking for them
|
||||
return clazz.javaClass.annotations
|
||||
.flatMap { it.javaClass.interfaces.map { it.simpleName } }
|
||||
.firstOrNull { it == Nsfw::class.java.simpleName } != null
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,12 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.resetStrokeColor
|
||||
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import kotlinx.android.synthetic.main.extension_card_item.*
|
||||
import kotlinx.android.synthetic.main.source_global_search_controller_card_item.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
|
||||
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||
BaseFlexibleViewHolder(view, adapter) {
|
||||
@ -26,17 +30,24 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||
}
|
||||
}
|
||||
|
||||
private val shouldLabelNsfw by lazy {
|
||||
Injekt.get<PreferencesHelper>().labelNsfwExtension()
|
||||
}
|
||||
|
||||
fun bind(item: ExtensionItem) {
|
||||
val extension = item.extension
|
||||
|
||||
// Set source name
|
||||
ext_title.text = extension.name
|
||||
version.text = extension.versionName
|
||||
lang.text = if (extension !is Extension.Untrusted) {
|
||||
LocaleHelper.getDisplayName(extension.lang, itemView.context)
|
||||
} else {
|
||||
itemView.context.getString(R.string.untrusted).toUpperCase()
|
||||
}
|
||||
lang.text = LocaleHelper.getDisplayName(extension.lang, itemView.context)
|
||||
warning.text = when {
|
||||
extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted)
|
||||
extension is Extension.Installed && extension.isObsolete -> itemView.context.getString(R.string.obsolete)
|
||||
extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.unofficial)
|
||||
extension.isNsfw && shouldLabelNsfw -> itemView.context.getString(R.string.nsfw_short)
|
||||
else -> ""
|
||||
}.toUpperCase(Locale.ROOT)
|
||||
|
||||
edit_button.clear()
|
||||
|
||||
@ -86,14 +97,8 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||
strokeColor = ColorStateList.valueOf(Color.TRANSPARENT)
|
||||
setText(R.string.update)
|
||||
}
|
||||
extension.isObsolete -> {
|
||||
// Red outline
|
||||
setTextColor(ContextCompat.getColorStateList(context, R.drawable.button_bg_error))
|
||||
|
||||
setText(R.string.obsolete)
|
||||
}
|
||||
else -> {
|
||||
setText(R.string.details)
|
||||
setText(R.string.settings)
|
||||
}
|
||||
}
|
||||
} else if (extension is Extension.Untrusted) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.DialogPreference
|
||||
@ -13,6 +14,8 @@ import androidx.preference.PreferenceManager
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.widget.preference.IntListMatPreference
|
||||
import eu.kanade.tachiyomi.widget.preference.ListMatPreference
|
||||
import eu.kanade.tachiyomi.widget.preference.MultiListMatPreference
|
||||
@ -86,6 +89,18 @@ inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).
|
||||
)
|
||||
}
|
||||
|
||||
inline fun PreferenceGroup.infoPreference(@StringRes infoRes: Int): Preference {
|
||||
return initThenAdd(
|
||||
Preference(context),
|
||||
{
|
||||
iconRes = R.drawable.ic_info_outline_24dp
|
||||
iconTint = context.getResourceColor(android.R.attr.textColorSecondary)
|
||||
summaryRes = infoRes
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
inline fun PreferenceScreen.preferenceScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
|
||||
return addThenInit(preferenceManager.createPreferenceScreen(context), block)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.setting
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.ui.migration.MigrationController
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SettingsBrowseController : SettingsController() {
|
||||
@ -105,12 +107,33 @@ class SettingsBrowseController : SettingsController() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
infoPreference(R.string.you_can_migrate_in_library)
|
||||
}
|
||||
preference {
|
||||
iconRes = R.drawable.ic_info_outline_24dp
|
||||
iconTint = activity?.getResourceColor(android.R.attr.textColorSecondary) ?: 0
|
||||
summaryRes = R.string.you_can_migrate_in_library
|
||||
isEnabled = false
|
||||
|
||||
preferenceCategory {
|
||||
titleRes = R.string.nsfw_sources
|
||||
|
||||
switchPreference {
|
||||
key = PreferenceKeys.showNsfwSource
|
||||
titleRes = R.string.show_in_sources
|
||||
summaryRes = R.string.requires_app_restart
|
||||
defaultValue = true
|
||||
}
|
||||
switchPreference {
|
||||
key = PreferenceKeys.showNsfwExtension
|
||||
titleRes = R.string.show_in_extensions
|
||||
defaultValue = true
|
||||
}
|
||||
switchPreference {
|
||||
key = PreferenceKeys.labelNsfwExtension
|
||||
titleRes = R.string.label_in_extensions
|
||||
defaultValue = true
|
||||
|
||||
preferences.showNsfwExtension().asImmediateFlow { isVisible = it }.launchIn(viewScope)
|
||||
}
|
||||
|
||||
infoPreference(R.string.does_not_prevent_unofficial_nsfw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,20 @@
|
||||
app:layout_constraintStart_toEndOf="@id/lang"
|
||||
tools:text="Version" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/warning"
|
||||
style="@style/TextAppearance.Regular.Body1.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/colorError"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintStart_toEndOf="@id/version"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ext_title"
|
||||
tools:text="Warning" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/ext_button"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
|
@ -263,6 +263,9 @@
|
||||
<string name="version_">Version: %1$s</string>
|
||||
<string name="language_">Language: %1$s</string>
|
||||
<string name="empty_preferences_for_extension">No preferences to edit for this extension</string>
|
||||
<string name="nsfw_short">18+</string>
|
||||
<string name="unofficial">Unofficial</string>
|
||||
<string name="may_contain_nsfw">May contain NSFW (18+) content</string>
|
||||
<plurals name="extension_updates_available">
|
||||
<item quantity="one">Extension update available</item>
|
||||
<item quantity="other">%d extension updates available</item>
|
||||
@ -622,6 +625,11 @@
|
||||
<string name="you_can_migrate_in_library">You can also migrate by selecting manga in your
|
||||
library</string>
|
||||
<string name="source_migration_guide">Source migration guide</string>
|
||||
<string name="nsfw_sources">NSFW (18+) sources</string>
|
||||
<string name="show_in_sources">Show in sources list</string>
|
||||
<string name="show_in_extensions">Show in extensions list</string>
|
||||
<string name="label_in_extensions">Label in extensions list</string>
|
||||
<string name="does_not_prevent_unofficial_nsfw">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string>
|
||||
|
||||
<!-- About section -->
|
||||
<string name="version">Version</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user