From 995f41f2df0e04259a8368c6ab5bd7d067a0d659 Mon Sep 17 00:00:00 2001 From: ziro Date: Wed, 24 Jan 2024 09:22:54 +0700 Subject: [PATCH] feat: Content type (NSFW/SFW) filter --- .../java/dev/yokai/util/LewdMangaChecker.kt | 31 +++++++++++++++++++ .../data/preference/PreferencesHelper.kt | 2 ++ .../tachiyomi/ui/library/LibraryPresenter.kt | 31 +++++++++++++++++-- .../ui/library/filter/FilterBottomSheet.kt | 13 +++++++- app/src/main/res/values/strings.xml | 5 ++- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/dev/yokai/util/LewdMangaChecker.kt diff --git a/app/src/main/java/dev/yokai/util/LewdMangaChecker.kt b/app/src/main/java/dev/yokai/util/LewdMangaChecker.kt new file mode 100644 index 0000000000..c25431fff9 --- /dev/null +++ b/app/src/main/java/dev/yokai/util/LewdMangaChecker.kt @@ -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().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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 9ec3cb4d69..a541719064 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -284,6 +284,8 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto 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 librarySortingMode() = preferenceStore.getInt("library_sorting_mode", 0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index d9b3cc5b31..e7a331d7f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.library +import dev.yokai.util.isLewd import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.getAndSet import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category @@ -121,7 +121,16 @@ class LibraryPresenter( 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 */ @@ -282,6 +291,8 @@ class LibraryPresenter( val filterMangaType = preferences.filterMangaType().get() + val filterContentType = preferences.filterContentType().get() + val filterBookmarked = preferences.filterBookmarked().get() val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get() @@ -289,7 +300,14 @@ class LibraryPresenter( val filterTrackers = FilterBottomSheet.FILTER_TRACKER 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 val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet() val filteredItems = items.filter f@{ item -> @@ -309,6 +327,7 @@ class LibraryPresenter( filterMangaType, filterBookmarked, filterTrackers, + filterContentType, ) } } @@ -329,6 +348,7 @@ class LibraryPresenter( filterMangaType, filterBookmarked, filterTrackers, + filterContentType, ) if (matches) { missingCategorySet.remove(item.manga.category) @@ -352,6 +372,7 @@ class LibraryPresenter( filterMangaType: Int, filterBookmarked: Int, filterTrackers: String, + filterContentType: Int, ): Boolean { (view as? FilteredLibraryController)?.let { return matchesCustomFilters(item, it, filterTrackers) @@ -393,6 +414,10 @@ class LibraryPresenter( } 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index 122ca9b61a..96e6711dbc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -73,6 +73,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri private lateinit var bookmarked: FilterTagGroup + private lateinit var contentType: FilterTagGroup + private var tracked: FilterTagGroup? = null private var trackers: FilterTagGroup? = null @@ -98,6 +100,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri if (hasTracking) { tracked?.let { list.add(it) } } + list.add(contentType) list } @@ -347,6 +350,9 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri 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() controller?.viewScope?.launch { @@ -436,6 +442,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri unreadProgress.state = unreadP - 3 } tracked?.setState(preferences.filterTracked()) + contentType.setState(preferences.filterContentType()) reorderFilters() reSortViews() } @@ -449,7 +456,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri filterItems.add(it) } } - listOfNotNull(unreadProgress, unread, downloaded, completed, mangaType, bookmarked, tracked) + listOfNotNull(unreadProgress, unread, downloaded, completed, mangaType, bookmarked, tracked, contentType) .forEach { if (!filterItems.contains(it)) { filterItems.add(it) @@ -470,6 +477,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri Filters.SeriesType -> mangaType Filters.Bookmarked -> bookmarked Filters.Tracked -> if (hasTracking) tracked else null + Filters.ContentType -> contentType else -> null } } @@ -531,6 +539,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri preferences.filterMangaType().set(newIndex) null } + contentType -> preferences.filterContentType() else -> null }?.set(index + 1) } @@ -624,6 +633,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri SeriesType('m', R.string.series_type), Bookmarked('b', R.string.bookmarked), Tracked('t', R.string.tracking), + ContentType('s', R.string.content_type); ; companion object { @@ -635,6 +645,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri SeriesType, Bookmarked, Tracked, + ContentType, ).joinToString("") { it.value.toString() } fun filterOf(char: Char) = entries.find { it.value == char } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61c03756f7..e80eb47a7a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1201,4 +1201,7 @@ Wi-Fi Webcomic Internal error: %s - + SFW + NSFW + Content Type + \ No newline at end of file