Highlight suspicious genres

This commit is contained in:
Koitharu
2023-02-25 17:15:07 +02:00
parent 672a1e9b2a
commit a3b22e050f
17 changed files with 117 additions and 34 deletions

View File

@@ -3,15 +3,16 @@ package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet
import android.view.View.OnClickListener
import androidx.annotation.DrawableRes
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.view.children
import com.google.android.material.R as materialR
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.castOrNull
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
import com.google.android.material.R as materialR
class ChipsView @JvmOverloads constructor(
context: Context,
@@ -75,12 +76,12 @@ class ChipsView @JvmOverloads constructor(
private fun bindChip(chip: Chip, model: ChipModel) {
chip.text = model.title
if (model.icon == 0) {
chip.isChipIconVisible = false
val tint = if (model.tint == 0) {
null
} else {
chip.isChipIconVisible = true
chip.setChipIconResource(model.icon)
ContextCompat.getColorStateList(context, model.tint)
}
chip.buttonTintList = tint
chip.isClickable = onChipClickListener != null || model.isCheckable
chip.isCheckable = model.isCheckable
chip.isChecked = model.isChecked
@@ -92,6 +93,7 @@ class ChipsView @JvmOverloads constructor(
val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip)
chip.setChipDrawable(drawable)
chip.isCheckedIconVisible = true
chip.isChipIconVisible = false
chip.setCheckedIconResource(R.drawable.ic_check)
chip.checkedIconTint = context.getThemeColorStateList(materialR.attr.colorControlNormal)
chip.isCloseIconVisible = onChipCloseClickListener != null
@@ -113,7 +115,7 @@ class ChipsView @JvmOverloads constructor(
}
class ChipModel(
@DrawableRes val icon: Int,
@ColorRes val tint: Int,
val title: CharSequence,
val isCheckable: Boolean,
val isChecked: Boolean,
@@ -126,7 +128,7 @@ class ChipsView @JvmOverloads constructor(
other as ChipModel
if (icon != other.icon) return false
if (tint != other.tint) return false
if (title != other.title) return false
if (isCheckable != other.isCheckable) return false
if (isChecked != other.isChecked) return false
@@ -136,7 +138,7 @@ class ChipsView @JvmOverloads constructor(
}
override fun hashCode(): Int {
var result = icon
var result = tint.hashCode()
result = 31 * result + title.hashCode()
result = 31 * result + isCheckable.hashCode()
result = 31 * result + isChecked.hashCode()

View File

@@ -0,0 +1,26 @@
package org.koitharu.kotatsu.core.parser
import android.content.Context
import androidx.annotation.ColorRes
import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.model.MangaTag
import javax.inject.Inject
@Reusable
class MangaTagHighlighter @Inject constructor(
@ApplicationContext context: Context,
) {
private val dict = context.resources.getStringArray(R.array.genres_warnlist).toSet()
@ColorRes
fun getTint(tag: MangaTag): Int {
return if (tag.title.lowercase() in dict) {
R.color.warning
} else {
0
}
}
}

View File

@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
@@ -69,6 +70,9 @@ class DetailsFragment :
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var tagHighlighter: MangaTagHighlighter
private val viewModel by activityViewModels<DetailsViewModel>()
override fun onInflateView(
@@ -321,7 +325,7 @@ class DetailsFragment :
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
icon = 0,
tint = tagHighlighter.getTint(tag),
data = tag,
isCheckable = false,
isChecked = false,

View File

@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
@@ -36,6 +37,7 @@ class FavouritesListViewModel @AssistedInject constructor(
private val trackingRepository: TrackingRepository,
private val historyRepository: HistoryRepository,
private val settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings), ListExtraProvider {
var categoryName: String? = null
@@ -71,7 +73,7 @@ class FavouritesListViewModel @AssistedInject constructor(
),
)
else -> list.toUi(mode, this)
else -> list.toUi(mode, this, tagHighlighter)
}
}.catch {
emit(listOf(it.toErrorState(canRetry = false)))

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
@@ -38,6 +39,7 @@ class HistoryListViewModel @Inject constructor(
private val repository: HistoryRepository,
private val settings: AppSettings,
private val trackingRepository: TrackingRepository,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings) {
val isGroupingEnabled = MutableLiveData<Boolean>()
@@ -118,7 +120,7 @@ class HistoryListViewModel @Inject constructor(
val percent = if (showPercent) history.percent else PROGRESS_NONE
result += when (mode) {
ListMode.LIST -> manga.toListModel(counter, percent)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent, tagHighlighter)
ListMode.GRID -> manga.toGridModel(counter, percent)
}
}

View File

@@ -4,6 +4,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.list.domain.ListExtraProvider
@@ -13,7 +14,10 @@ import org.koitharu.kotatsu.utils.ext.ifZero
import java.net.SocketTimeoutException
import java.net.UnknownHostException
fun Manga.toListModel(counter: Int, progress: Float) = MangaListModel(
fun Manga.toListModel(
counter: Int,
progress: Float,
) = MangaListModel(
id = id,
title = title,
subtitle = tags.joinToString(", ") { it.title },
@@ -23,7 +27,11 @@ fun Manga.toListModel(counter: Int, progress: Float) = MangaListModel(
progress = progress,
)
fun Manga.toListDetailedModel(counter: Int, progress: Float) = MangaListDetailedModel(
fun Manga.toListDetailedModel(
counter: Int,
progress: Float,
tagHighlighter: MangaTagHighlighter?,
) = MangaListDetailedModel(
id = id,
title = title,
subtitle = altTitle,
@@ -31,7 +39,15 @@ fun Manga.toListDetailedModel(counter: Int, progress: Float) = MangaListDetailed
manga = this,
counter = counter,
progress = progress,
tags = tags.map { ChipsView.ChipModel(0, it.title, false, false, it) },
tags = tags.map {
ChipsView.ChipModel(
tint = tagHighlighter?.getTint(it) ?: 0,
title = it.title,
isCheckable = false,
isChecked = false,
data = it,
)
},
)
fun Manga.toGridModel(counter: Int, progress: Float) = MangaGridModel(
@@ -46,18 +62,21 @@ fun Manga.toGridModel(counter: Int, progress: Float) = MangaGridModel(
suspend fun List<Manga>.toUi(
mode: ListMode,
extraProvider: ListExtraProvider,
): List<MangaItemModel> = toUi(ArrayList(size), mode, extraProvider)
tagHighlighter: MangaTagHighlighter?,
): List<MangaItemModel> = toUi(ArrayList(size), mode, extraProvider, tagHighlighter)
fun List<Manga>.toUi(
mode: ListMode,
): List<MangaItemModel> = toUi(ArrayList(size), mode)
tagHighlighter: MangaTagHighlighter?,
): List<MangaItemModel> = toUi(ArrayList(size), mode, tagHighlighter)
fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
destination: C,
mode: ListMode,
tagHighlighter: MangaTagHighlighter?,
): C = when (mode) {
ListMode.LIST -> mapTo(destination) { it.toListModel(0, PROGRESS_NONE) }
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0, PROGRESS_NONE) }
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0, PROGRESS_NONE, tagHighlighter) }
ListMode.GRID -> mapTo(destination) { it.toGridModel(0, PROGRESS_NONE) }
}
@@ -65,13 +84,14 @@ suspend fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
destination: C,
mode: ListMode,
extraProvider: ListExtraProvider,
tagHighlighter: MangaTagHighlighter?,
): C = when (mode) {
ListMode.LIST -> mapTo(destination) {
it.toListModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
}
ListMode.DETAILED_LIST -> mapTo(destination) {
it.toListDetailedModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
it.toListDetailedModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id), tagHighlighter)
}
ListMode.GRID -> mapTo(destination) {

View File

@@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.history.domain.HistoryRepository
@@ -46,6 +47,7 @@ class LocalListViewModel @Inject constructor(
private val historyRepository: HistoryRepository,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings), ListExtraProvider {
val onMangaRemoved = SingleLiveEvent<Unit>()
@@ -76,7 +78,7 @@ class LocalListViewModel @Inject constructor(
else -> buildList(list.size + 1) {
add(createHeader(list, tags, order))
list.toUi(this, mode, this@LocalListViewModel)
list.toUi(this, mode, this@LocalListViewModel, tagHighlighter)
}
}
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
@@ -170,7 +172,7 @@ class LocalListViewModel @Inject constructor(
val chips = LinkedList<ChipsView.ChipModel>()
for ((tag, _) in topTags) {
val model = ChipsView.ChipModel(
icon = 0,
tint = 0,
title = tag.title,
isCheckable = true,
isChecked = tag in selectedTags,

View File

@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel
@@ -49,6 +50,7 @@ class RemoteListViewModel @AssistedInject constructor(
private val searchRepository: MangaSearchRepository,
settings: AppSettings,
dataRepository: MangaDataRepository,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings), OnFilterChangedListener {
private val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
@@ -75,7 +77,7 @@ class RemoteListViewModel @AssistedInject constructor(
list == null -> add(LoadingState)
list.isEmpty() -> add(createEmptyState(header.hasSelectedTags))
else -> {
list.toUi(this, mode)
list.toUi(this, mode, tagHighlighter)
when {
error != null -> add(error.toErrorFooter())
hasNext -> add(LoadingFooter)
@@ -192,7 +194,7 @@ class RemoteListViewModel @AssistedInject constructor(
val result = LinkedList<ChipsView.ChipModel>()
for (tag in tags) {
val model = ChipsView.ChipModel(
icon = 0,
tint = 0,
title = tag.title,
isCheckable = true,
isChecked = selectedTags.remove(tag),
@@ -206,7 +208,7 @@ class RemoteListViewModel @AssistedInject constructor(
}
for (tag in selectedTags) {
val model = ChipsView.ChipModel(
icon = 0,
tint = 0,
title = tag.title,
isCheckable = true,
isChecked = true,

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -29,6 +30,7 @@ class SearchViewModel @AssistedInject constructor(
@Assisted private val query: String,
repositoryFactory: MangaRepository.Factory,
settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings) {
private val repository = repositoryFactory.create(source)
@@ -57,7 +59,7 @@ class SearchViewModel @AssistedInject constructor(
else -> {
val result = ArrayList<ListModel>(list.size + 1)
list.toUi(result, mode)
list.toUi(result, mode, tagHighlighter)
when {
error != null -> result += error.toErrorFooter()
hasNext -> result += LoadingFooter

View File

@@ -6,7 +6,12 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
@@ -16,7 +21,12 @@ import org.koitharu.kotatsu.core.exceptions.CompositeException
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
@@ -104,7 +114,7 @@ class MultiSearchViewModel @AssistedInject constructor(
async(dispatcher) {
runCatchingCancellable {
val list = mangaRepositoryFactory.create(source).getList(offset = 0, query = q)
.toUi(ListMode.GRID)
.toUi(ListMode.GRID, null)
if (list.isNotEmpty()) {
MultiSearchListModel(source, list.size > MIN_HAS_MORE_ITEMS, list)
} else {

View File

@@ -134,7 +134,7 @@ class SearchSuggestionViewModel @Inject constructor(
private fun mapTags(tags: List<MangaTag>): List<ChipsView.ChipModel> = tags.map { tag ->
ChipsView.ChipModel(
icon = 0,
tint = 0,
title = tag.title,
data = tag,
isCheckable = false,

View File

@@ -236,7 +236,7 @@ class ShelfViewModel @Inject constructor(
return
}
destination += ShelfSectionModel.Local(
items = local.toUi(ListMode.GRID, this),
items = local.toUi(ListMode.GRID, this, null),
showAllButtonText = R.string.show_all,
)
}
@@ -251,7 +251,7 @@ class ShelfViewModel @Inject constructor(
for ((category, list) in favourites) {
if (list.isNotEmpty()) {
destination += ShelfSectionModel.Favourites(
items = list.toUi(ListMode.GRID, this),
items = list.toUi(ListMode.GRID, this, null),
category = category,
showAllButtonText = R.string.show_all,
)

View File

@@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -22,6 +23,7 @@ import javax.inject.Inject
class SuggestionsViewModel @Inject constructor(
repository: SuggestionRepository,
settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings) {
override val content = combine(
@@ -38,7 +40,7 @@ class SuggestionsViewModel @Inject constructor(
),
)
else -> list.toUi(mode)
else -> list.toUi(mode, tagHighlighter)
}
}.onStart {
loadingCounter.increment()

View File

@@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.history.domain.HistoryRepository
@@ -30,6 +31,7 @@ class UpdatesViewModel @Inject constructor(
private val repository: TrackingRepository,
private val settings: AppSettings,
private val historyRepository: HistoryRepository,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings) {
override val content = combine(
@@ -69,7 +71,7 @@ class UpdatesViewModel @Inject constructor(
val percent = if (showPercent) historyRepository.getProgress(manga.id) else PROGRESS_NONE
when (mode) {
ListMode.LIST -> manga.toListModel(counter, percent)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent, tagHighlighter)
ListMode.GRID -> manga.toGridModel(counter, percent)
}
}

View File

@@ -9,5 +9,5 @@
<color name="selector_foreground">#29FFFFFF</color>
<color name="divider_default">#1FFFFFFF</color>
<color name="status_bar_incognito">#260052</color>
<color name="warning">#EF6C00</color>
</resources>

View File

@@ -22,4 +22,5 @@
<color name="selector_foreground">#29000000</color>
<color name="divider_default">#1F000000</color>
<color name="status_bar_incognito">#334800E0</color>
<color name="warning">#FFA726</color>
</resources>

View File

@@ -38,4 +38,10 @@
<item>2</item>
<item>0</item>
</string-array>
<string-array name="genres_warnlist" translatable="false">
<item>yaoi</item>
<item>яой</item>
<item>yuri</item>
<item>юри</item>
</string-array>
</resources>