Option to hide nsfw content
This commit is contained in:
@@ -9,7 +9,6 @@ import android.provider.Settings
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.collection.arraySetOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
@@ -56,6 +55,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
get() = prefs.getInt(KEY_GRID_SIZE, 100)
|
||||
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
|
||||
|
||||
var isNsfwContentDisabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_DISABLE_NSFW, false)
|
||||
set(value) = prefs.edit { putBoolean(KEY_DISABLE_NSFW, value) }
|
||||
|
||||
var appLocales: LocaleListCompat
|
||||
get() {
|
||||
val raw = prefs.getString(KEY_APP_LOCALE, null)
|
||||
@@ -444,6 +447,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_PROXY_PASSWORD = "proxy_password"
|
||||
const val KEY_IMAGES_PROXY = "images_proxy"
|
||||
const val KEY_LOCAL_MANGA_DIRS = "local_manga_dirs"
|
||||
const val KEY_DISABLE_NSFW = "no_nsfw"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -4,13 +4,17 @@ import androidx.room.withTransaction
|
||||
import dagger.Reusable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.move
|
||||
import java.util.Collections
|
||||
@@ -20,6 +24,7 @@ import javax.inject.Inject
|
||||
@Reusable
|
||||
class MangaSourcesRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
|
||||
private val dao: MangaSourcesDao
|
||||
@@ -36,11 +41,13 @@ class MangaSourcesRepository @Inject constructor(
|
||||
get() = Collections.unmodifiableSet(remoteSources)
|
||||
|
||||
suspend fun getEnabledSources(): List<MangaSource> {
|
||||
return dao.findAllEnabled().toSources()
|
||||
return dao.findAllEnabled().toSources(settings.isNsfwContentDisabled)
|
||||
}
|
||||
|
||||
fun observeEnabledSources(): Flow<List<MangaSource>> = dao.observeEnabled().map {
|
||||
it.toSources()
|
||||
fun observeEnabledSources(): Flow<List<MangaSource>> = observeIsNsfwDisabled().flatMapLatest { skipNsfw ->
|
||||
dao.observeEnabled().map {
|
||||
it.toSources(skipNsfw)
|
||||
}
|
||||
}
|
||||
|
||||
fun observeAll(): Flow<List<Pair<MangaSource, Boolean>>> = dao.observeAll().map { entities ->
|
||||
@@ -137,14 +144,21 @@ class MangaSourcesRepository @Inject constructor(
|
||||
return dao.findAll().isEmpty()
|
||||
}
|
||||
|
||||
private fun List<MangaSourceEntity>.toSources(): List<MangaSource> {
|
||||
private fun List<MangaSourceEntity>.toSources(skipNsfwSources: Boolean): List<MangaSource> {
|
||||
val result = ArrayList<MangaSource>(size)
|
||||
for (entity in this) {
|
||||
val source = MangaSource(entity.source)
|
||||
if (skipNsfwSources && source.contentType == ContentType.HENTAI) {
|
||||
continue
|
||||
}
|
||||
if (source in remoteSources) {
|
||||
result.add(source)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun observeIsNsfwDisabled() = settings.observeAsFlow(AppSettings.KEY_DISABLE_NSFW) {
|
||||
isNsfwContentDisabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
|
||||
abstract class MangaListViewModel(
|
||||
settings: AppSettings,
|
||||
private val settings: AppSettings,
|
||||
private val downloadScheduler: DownloadWorker.Scheduler,
|
||||
) : BaseViewModel() {
|
||||
|
||||
@@ -46,4 +46,10 @@ abstract class MangaListViewModel(
|
||||
onDownloadStarted.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun List<Manga>.skipNsfwIfNeeded() = if (settings.isNsfwContentDisabled) {
|
||||
filterNot { it.isNsfw }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
@@ -66,7 +67,7 @@ open class RemoteListViewModel @Inject constructor(
|
||||
private var randomJob: Job? = null
|
||||
|
||||
override val content = combine(
|
||||
mangaList,
|
||||
mangaList.map { it?.skipNsfwIfNeeded() },
|
||||
listMode,
|
||||
listError,
|
||||
hasNextPage,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.search.domain
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.provider.SearchRecentSuggestions
|
||||
@@ -8,19 +7,18 @@ import dagger.Reusable
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toMangaTag
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -30,34 +28,22 @@ class MangaSearchRepository @Inject constructor(
|
||||
private val sourcesRepository: MangaSourcesRepository,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val recentSuggestions: SearchRecentSuggestions,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
|
||||
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
|
||||
flow {
|
||||
emitAll(sourcesRepository.getEnabledSources().asFlow())
|
||||
}.flatMapMerge(concurrency) { source ->
|
||||
runCatchingCancellable {
|
||||
mangaRepositoryFactory.create(source).getList(
|
||||
offset = 0,
|
||||
query = query,
|
||||
)
|
||||
}.getOrElse {
|
||||
emptyList()
|
||||
}.asFlow()
|
||||
}.filter {
|
||||
match(it, query)
|
||||
}
|
||||
|
||||
suspend fun getMangaSuggestion(query: String, limit: Int, source: MangaSource?): List<Manga> {
|
||||
if (query.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val skipNsfw = settings.isNsfwContentDisabled
|
||||
return if (source != null) {
|
||||
db.mangaDao.searchByTitle("%$query%", source.name, limit)
|
||||
} else {
|
||||
db.mangaDao.searchByTitle("%$query%", limit)
|
||||
}.map { it.toManga() }
|
||||
}.let {
|
||||
if (skipNsfw) it.filterNot { x -> x.manga.isNsfw } else it
|
||||
}
|
||||
.map { it.toManga() }
|
||||
.sortedBy { x -> x.title.levenshteinDistance(query) }
|
||||
}
|
||||
|
||||
@@ -67,7 +53,7 @@ class MangaSearchRepository @Inject constructor(
|
||||
): List<String> = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.query(
|
||||
MangaSuggestionsProvider.QUERY_URI,
|
||||
SUGGESTION_PROJECTION,
|
||||
arrayOf(SearchManager.SUGGEST_COLUMN_QUERY),
|
||||
"${SearchManager.SUGGEST_COLUMN_QUERY} LIKE ?",
|
||||
arrayOf("%$query%"),
|
||||
"date DESC",
|
||||
@@ -102,8 +88,11 @@ class MangaSearchRepository @Inject constructor(
|
||||
if (query.length < 3) {
|
||||
return emptyList()
|
||||
}
|
||||
val skipNsfw = settings.isNsfwContentDisabled
|
||||
val sources = sourcesRepository.allMangaSources
|
||||
.filter { x -> x.title.contains(query, ignoreCase = true) }
|
||||
.filter { x ->
|
||||
(x.contentType != ContentType.HENTAI || !skipNsfw) && x.title.contains(query, ignoreCase = true)
|
||||
}
|
||||
return if (limit == 0) {
|
||||
sources
|
||||
} else {
|
||||
@@ -130,33 +119,10 @@ class MangaSearchRepository @Inject constructor(
|
||||
suspend fun getSearchHistoryCount(): Int = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.query(
|
||||
MangaSuggestionsProvider.QUERY_URI,
|
||||
SUGGESTION_PROJECTION,
|
||||
arrayOf(SearchManager.SUGGEST_COLUMN_QUERY),
|
||||
null,
|
||||
arrayOfNulls(1),
|
||||
null,
|
||||
)?.use { cursor -> cursor.count } ?: 0
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private val REGEX_SPACE = Regex("\\s+")
|
||||
val SUGGESTION_PROJECTION = arrayOf(SearchManager.SUGGEST_COLUMN_QUERY)
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun match(manga: Manga, query: String): Boolean {
|
||||
val words = HashSet<String>()
|
||||
words += manga.title.lowercase().split(REGEX_SPACE)
|
||||
words += manga.altTitle?.lowercase()?.split(REGEX_SPACE).orEmpty()
|
||||
val words2 = query.lowercase().split(REGEX_SPACE).toSet()
|
||||
for (w in words) {
|
||||
for (w2 in words2) {
|
||||
val diff = w.levenshteinDistance(w2) / ((w.length + w2.length) / 2f)
|
||||
if (diff < 0.5) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_no_nsfw"
|
||||
android:checkable="true"
|
||||
android:title="@string/disable_nsfw"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_locales"
|
||||
android:title="@string/languages"
|
||||
|
||||
@@ -472,4 +472,5 @@
|
||||
<string name="languages">Languages</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="in_progress">In progress</string>
|
||||
<string name="disable_nsfw">Disable NSFW</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user