Option to hide nsfw content

This commit is contained in:
Koitharu
2023-08-11 14:50:56 +03:00
parent 03cb458d92
commit caebca36de
12 changed files with 104 additions and 129 deletions

View File

@@ -9,8 +9,10 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.util.SuspendLazy
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
import javax.inject.Inject
@@ -18,6 +20,7 @@ import javax.inject.Inject
@HiltViewModel
class NewSourcesViewModel @Inject constructor(
private val repository: MangaSourcesRepository,
private val settings: AppSettings,
) : BaseViewModel() {
private val newSources = SuspendLazy {
@@ -26,9 +29,16 @@ class NewSourcesViewModel @Inject constructor(
val content: StateFlow<List<SourceConfigItem>> = repository.observeAll()
.map { sources ->
val new = newSources.get()
val skipNsfw = settings.isNsfwContentDisabled
sources.mapNotNull { (source, enabled) ->
if (source in new) {
SourceConfigItem.SourceItem(source, enabled, source.getLocaleTitle(), false)
SourceConfigItem.SourceItem(
source = source,
isEnabled = enabled,
summary = source.getLocaleTitle(),
isDraggable = false,
isAvailable = !skipNsfw || source.contentType != ContentType.HENTAI,
)
} else {
null
}

View File

@@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
@@ -41,6 +42,9 @@ class SourcesManageFragment :
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var settings: AppSettings
private var reorderHelper: ItemTouchHelper? = null
private val viewModel by viewModels<SourcesManageViewModel>()
@@ -128,9 +132,19 @@ class SourcesManageFragment :
true
}
R.id.action_no_nsfw -> {
settings.isNsfwContentDisabled = !menuItem.isChecked
true
}
else -> false
}
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
menu.findItem(R.id.action_no_nsfw).isChecked = settings.isNsfwContentDisabled
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
(activity as? AppBarOwner)?.appBar?.setExpanded(false, true)
return true

View File

@@ -27,6 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.map
import org.koitharu.kotatsu.core.util.ext.toEnumSet
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
@@ -54,8 +55,9 @@ class SourcesManageViewModel @Inject constructor(
expandedGroups,
searchQuery,
observeTip(),
) { sources, groups, query, tip ->
buildList(sources, groups, query, tip)
settings.observeAsFlow(AppSettings.KEY_DISABLE_NSFW) { isNsfwContentDisabled },
) { sources, groups, query, tip, noNsfw ->
buildList(sources, groups, query, tip, noNsfw)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val onActionDone = MutableEventFlow<ReversibleAction>()
@@ -125,6 +127,7 @@ class SourcesManageViewModel @Inject constructor(
expanded: Set<String?>,
query: String?,
withTip: Boolean,
isNsfwDisabled: Boolean,
): List<SourceConfigItem> {
val allSources = repository.allMangaSources
val enabledSet = enabledSources.toEnumSet()
@@ -138,6 +141,7 @@ class SourcesManageViewModel @Inject constructor(
summary = it.getLocaleTitle(),
isEnabled = it in enabledSet,
isDraggable = false,
isAvailable = !isNsfwDisabled || !it.isNsfw(),
)
}.ifEmpty {
listOf(SourceConfigItem.EmptySearchResult)
@@ -163,6 +167,7 @@ class SourcesManageViewModel @Inject constructor(
summary = it.getLocaleTitle(),
isEnabled = true,
isDraggable = true,
isAvailable = false,
)
}
}
@@ -184,6 +189,7 @@ class SourcesManageViewModel @Inject constructor(
summary = null,
isEnabled = false,
isDraggable = false,
isAvailable = !isNsfwDisabled || !it.isNsfw(),
)
}
}
@@ -210,6 +216,8 @@ class SourcesManageViewModel @Inject constructor(
isTipEnabled(TIP_REORDER)
}
private fun MangaSource.isNsfw() = contentType == ContentType.HENTAI
private class LocaleKeyComparator : Comparator<String?> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()

View File

@@ -72,8 +72,17 @@ fun sourceConfigItemCheckableDelegate(
}
bind {
binding.textViewTitle.text = item.source.title
binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.switchToggle.isChecked = item.isEnabled
binding.switchToggle.isEnabled = item.isAvailable
binding.textViewDescription.textAndVisible = item.summary
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
@@ -120,7 +129,7 @@ fun sourceConfigItemDelegate2(
} else {
item.source.title
}
binding.imageViewAdd.isGone = item.isEnabled
binding.imageViewAdd.isGone = item.isEnabled || !item.isAvailable
binding.imageViewRemove.isVisible = item.isEnabled
binding.imageViewConfig.isVisible = item.isEnabled
binding.textViewDescription.textAndVisible = item.summary

View File

@@ -9,25 +9,16 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface SourceConfigItem : ListModel {
class Header(
data class Header(
@StringRes val titleResId: Int,
) : SourceConfigItem {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Header && other.titleResId == titleResId
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Header
return titleResId == other.titleResId
}
override fun hashCode(): Int = titleResId
}
class LocaleGroup(
data class LocaleGroup(
val localeId: String?,
val title: String?,
val isExpanded: Boolean,
@@ -44,31 +35,14 @@ sealed interface SourceConfigItem : ListModel {
super.getChangePayload(previousState)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LocaleGroup
if (localeId != other.localeId) return false
if (title != other.title) return false
return isExpanded == other.isExpanded
}
override fun hashCode(): Int {
var result = localeId?.hashCode() ?: 0
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + isExpanded.hashCode()
return result
}
}
class SourceItem(
data class SourceItem(
val source: MangaSource,
val isEnabled: Boolean,
val summary: String?,
val isDraggable: Boolean,
val isAvailable: Boolean,
) : SourceConfigItem {
val isNsfw: Boolean
@@ -85,29 +59,9 @@ sealed interface SourceConfigItem : ListModel {
super.getChangePayload(previousState)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SourceItem
if (source != other.source) return false
if (summary != other.summary) return false
if (isEnabled != other.isEnabled) return false
return isDraggable == other.isDraggable
}
override fun hashCode(): Int {
var result = source.hashCode()
result = 31 * result + summary.hashCode()
result = 31 * result + isEnabled.hashCode()
result = 31 * result + isDraggable.hashCode()
return result
}
}
class Tip(
data class Tip(
val key: String,
@DrawableRes val iconResId: Int,
@StringRes val textResId: Int,
@@ -116,24 +70,6 @@ sealed interface SourceConfigItem : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Tip && other.key == key
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Tip
if (key != other.key) return false
if (iconResId != other.iconResId) return false
return textResId == other.textResId
}
override fun hashCode(): Int {
var result = key.hashCode()
result = 31 * result + iconResId
result = 31 * result + textResId
return result
}
}
object EmptySearchResult : SourceConfigItem {