feat: Content type (NSFW/SFW) filter

This commit is contained in:
ziro 2024-01-24 09:22:54 +07:00
parent ff30fa5683
commit 995f41f2df
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
5 changed files with 77 additions and 5 deletions

View file

@ -0,0 +1,31 @@
package dev.yokai.util
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Locale
fun Manga.isLewd(): Boolean {
val sourceName = Injekt.get<SourceManager>().get(source)?.name
val tags = genre?.split(",")?.map { it.trim().lowercase(Locale.US) } ?: emptyList()
if (!tags.none { isNonHentai(it) }) return false
return (sourceName != null && sourceName.isFromHentaiSource()) || tags.any { isHentai(it) }
}
private fun isNonHentai(tag: String) = tag.contains("non-h", true)
private fun String.isFromHentaiSource() =
contains("hentai", true) ||
contains("adult", true)
private fun isHentai(tag: String) =
tag.contains("hentai", true) ||
tag.contains("adult", true) ||
tag.contains("smut", true) ||
tag.contains("lewd", true) ||
tag.contains("nsfw", true) ||
tag.contains("erotic", true) ||
tag.contains("pornographic", true) ||
tag.contains("18+", true)

View file

@ -284,6 +284,8 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun filterMangaType() = preferenceStore.getInt(Keys.filterMangaType, 0) fun filterMangaType() = preferenceStore.getInt(Keys.filterMangaType, 0)
fun filterContentType() = preferenceStore.getInt("pref_filter_content_type_key", 0)
fun showEmptyCategoriesWhileFiltering() = preferenceStore.getBoolean(Keys.showEmptyCategoriesFiltering, false) fun showEmptyCategoriesWhileFiltering() = preferenceStore.getBoolean(Keys.showEmptyCategoriesFiltering, false)
fun librarySortingMode() = preferenceStore.getInt("library_sorting_mode", 0) fun librarySortingMode() = preferenceStore.getInt("library_sorting_mode", 0)

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import dev.yokai.util.isLewd
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.preference.getAndSet
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -121,7 +121,16 @@ class LibraryPresenter(
val filterMangaType = preferences.filterMangaType().get() val filterMangaType = preferences.filterMangaType().get()
!(filterDownloaded == 0 && filterUnread == 0 && filterCompleted == 0 && filterTracked == 0 && filterMangaType == 0) val filterContentType = preferences.filterContentType().get()
!(
filterDownloaded == 0 &&
filterUnread == 0 &&
filterCompleted == 0 &&
filterTracked == 0 &&
filterMangaType == 0 &&
filterContentType == 0
)
} }
/** Save the current list to speed up loading later */ /** Save the current list to speed up loading later */
@ -282,6 +291,8 @@ class LibraryPresenter(
val filterMangaType = preferences.filterMangaType().get() val filterMangaType = preferences.filterMangaType().get()
val filterContentType = preferences.filterContentType().get()
val filterBookmarked = preferences.filterBookmarked().get() val filterBookmarked = preferences.filterBookmarked().get()
val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get() val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get()
@ -289,7 +300,14 @@ class LibraryPresenter(
val filterTrackers = FilterBottomSheet.FILTER_TRACKER val filterTrackers = FilterBottomSheet.FILTER_TRACKER
val filtersOff = view?.isSubClass != true && val filtersOff = view?.isSubClass != true &&
(filterDownloaded == 0 && filterUnread == 0 && filterCompleted == 0 && filterTracked == 0 && filterMangaType == 0) (
filterDownloaded == 0 &&
filterUnread == 0 &&
filterCompleted == 0 &&
filterTracked == 0 &&
filterMangaType == 0 &&
filterContentType == 0
)
hasActiveFilters = !filtersOff hasActiveFilters = !filtersOff
val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet() val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet()
val filteredItems = items.filter f@{ item -> val filteredItems = items.filter f@{ item ->
@ -309,6 +327,7 @@ class LibraryPresenter(
filterMangaType, filterMangaType,
filterBookmarked, filterBookmarked,
filterTrackers, filterTrackers,
filterContentType,
) )
} }
} }
@ -329,6 +348,7 @@ class LibraryPresenter(
filterMangaType, filterMangaType,
filterBookmarked, filterBookmarked,
filterTrackers, filterTrackers,
filterContentType,
) )
if (matches) { if (matches) {
missingCategorySet.remove(item.manga.category) missingCategorySet.remove(item.manga.category)
@ -352,6 +372,7 @@ class LibraryPresenter(
filterMangaType: Int, filterMangaType: Int,
filterBookmarked: Int, filterBookmarked: Int,
filterTrackers: String, filterTrackers: String,
filterContentType: Int,
): Boolean { ): Boolean {
(view as? FilteredLibraryController)?.let { (view as? FilteredLibraryController)?.let {
return matchesCustomFilters(item, it, filterTrackers) return matchesCustomFilters(item, it, filterTrackers)
@ -393,6 +414,10 @@ class LibraryPresenter(
} }
return if (filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded return if (filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded
} }
// Filter for NSFW/SFW contents
if (filterContentType == STATE_INCLUDE) return !item.manga.isLewd()
if (filterContentType == STATE_EXCLUDE) return item.manga.isLewd()
return true return true
} }

View file

@ -73,6 +73,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
private lateinit var bookmarked: FilterTagGroup private lateinit var bookmarked: FilterTagGroup
private lateinit var contentType: FilterTagGroup
private var tracked: FilterTagGroup? = null private var tracked: FilterTagGroup? = null
private var trackers: FilterTagGroup? = null private var trackers: FilterTagGroup? = null
@ -98,6 +100,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
if (hasTracking) { if (hasTracking) {
tracked?.let { list.add(it) } tracked?.let { list.add(it) }
} }
list.add(contentType)
list list
} }
@ -347,6 +350,9 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
tracked?.setup(this, R.string.tracked, R.string.not_tracked) tracked?.setup(this, R.string.tracked, R.string.not_tracked)
} }
contentType = inflate(R.layout.filter_tag_group) as FilterTagGroup
contentType.setup(this, R.string.sfw, R.string.nsfw)
reSortViews() reSortViews()
controller?.viewScope?.launch { controller?.viewScope?.launch {
@ -436,6 +442,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
unreadProgress.state = unreadP - 3 unreadProgress.state = unreadP - 3
} }
tracked?.setState(preferences.filterTracked()) tracked?.setState(preferences.filterTracked())
contentType.setState(preferences.filterContentType())
reorderFilters() reorderFilters()
reSortViews() reSortViews()
} }
@ -449,7 +456,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
filterItems.add(it) filterItems.add(it)
} }
} }
listOfNotNull(unreadProgress, unread, downloaded, completed, mangaType, bookmarked, tracked) listOfNotNull(unreadProgress, unread, downloaded, completed, mangaType, bookmarked, tracked, contentType)
.forEach { .forEach {
if (!filterItems.contains(it)) { if (!filterItems.contains(it)) {
filterItems.add(it) filterItems.add(it)
@ -470,6 +477,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
Filters.SeriesType -> mangaType Filters.SeriesType -> mangaType
Filters.Bookmarked -> bookmarked Filters.Bookmarked -> bookmarked
Filters.Tracked -> if (hasTracking) tracked else null Filters.Tracked -> if (hasTracking) tracked else null
Filters.ContentType -> contentType
else -> null else -> null
} }
} }
@ -531,6 +539,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
preferences.filterMangaType().set(newIndex) preferences.filterMangaType().set(newIndex)
null null
} }
contentType -> preferences.filterContentType()
else -> null else -> null
}?.set(index + 1) }?.set(index + 1)
} }
@ -624,6 +633,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
SeriesType('m', R.string.series_type), SeriesType('m', R.string.series_type),
Bookmarked('b', R.string.bookmarked), Bookmarked('b', R.string.bookmarked),
Tracked('t', R.string.tracking), Tracked('t', R.string.tracking),
ContentType('s', R.string.content_type);
; ;
companion object { companion object {
@ -635,6 +645,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
SeriesType, SeriesType,
Bookmarked, Bookmarked,
Tracked, Tracked,
ContentType,
).joinToString("") { it.value.toString() } ).joinToString("") { it.value.toString() }
fun filterOf(char: Char) = entries.find { it.value == char } fun filterOf(char: Char) = entries.find { it.value == char }

View file

@ -1201,4 +1201,7 @@
<string name="wifi">Wi-Fi</string> <string name="wifi">Wi-Fi</string>
<string name="webcomic">Webcomic</string> <string name="webcomic">Webcomic</string>
<string name="internal_error">Internal error: %s</string> <string name="internal_error">Internal error: %s</string>
<string name="sfw">SFW</string>
<string name="nsfw">NSFW</string>
<string name="content_type">Content Type</string>
</resources> </resources>