Sources catalog improvements

master
Koitharu 2 years ago
parent 8d7bad97de
commit 173087ee19
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -82,7 +82,7 @@ afterEvaluate {
} }
dependencies { dependencies {
//noinspection GradleDependency //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:77a733a062') { implementation('com.github.KotatsuApp:kotatsu-parsers:0b2bf607f7') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

@ -15,8 +15,6 @@ import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.toLocale import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
fun MangaSource(name: String): MangaSource { fun MangaSource(name: String): MangaSource {
@ -39,7 +37,7 @@ val ContentType.titleResId
fun MangaSource.getSummary(context: Context): String { fun MangaSource.getSummary(context: Context): String {
val type = context.getString(contentType.titleResId) val type = context.getString(contentType.titleResId)
val locale = locale?.toLocale().getDisplayName(context) val locale = locale.toLocale().getDisplayName(context)
return context.getString(R.string.source_summary_pattern, type, locale) return context.getString(R.string.source_summary_pattern, type, locale)
} }

@ -52,6 +52,16 @@ class FastScrollRecyclerView @JvmOverloads constructor(
fastScroller.visibility = if (isFastScrollerEnabled) visibility else GONE fastScroller.visibility = if (isFastScrollerEnabled) visibility else GONE
} }
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, bottom)
fastScroller.setPadding(left, top, right, bottom)
}
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
super.setPaddingRelative(start, top, end, bottom)
fastScroller.setPaddingRelative(start, top, end, bottom)
}
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
fastScroller.attachRecyclerView(this) fastScroller.attachRecyclerView(this)

@ -26,7 +26,9 @@ class ChipsView @JvmOverloads constructor(
onChipClickListener?.onChipClick(it as Chip, it.tag) onChipClickListener?.onChipClick(it as Chip, it.tag)
} }
private val chipOnCloseListener = OnClickListener { private val chipOnCloseListener = OnClickListener {
onChipCloseClickListener?.onChipCloseClick(it as Chip, it.tag) val chip = it as Chip
val data = it.tag
onChipCloseClickListener?.onChipCloseClick(chip, data) ?: onChipClickListener?.onChipClick(chip, data)
} }
private val chipStyle: Int private val chipStyle: Int
var onChipClickListener: OnChipClickListener? = null var onChipClickListener: OnChipClickListener? = null

@ -1,14 +1,27 @@
package org.koitharu.kotatsu.core.util package org.koitharu.kotatsu.core.util
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import org.koitharu.kotatsu.core.util.ext.map import org.koitharu.kotatsu.core.util.ext.iterator
import java.util.Locale import java.util.Locale
class LocaleComparator : Comparator<Locale> { class LocaleComparator : Comparator<Locale> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()//LocaleManagerCompat.getSystemLocales(context) private val deviceLocales: List<String>
.map { it.language }
.distinct() init {
val localeList = LocaleListCompat.getAdjustedDefault()
deviceLocales = buildList(localeList.size() + 1) {
add("")
val set = HashSet<String>(localeList.size() + 1)
set.add("")
for (locale in localeList) {
val lang = locale.language
if (set.add(lang)) {
add(lang)
}
}
}
}
override fun compare(a: Locale, b: Locale): Int { override fun compare(a: Locale, b: Locale): Int {
val indexA = deviceLocales.indexOf(a.language) val indexA = deviceLocales.indexOf(a.language)

@ -22,11 +22,10 @@ fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementE
fun String.toLocale() = Locale(this) fun String.toLocale() = Locale(this)
fun Locale?.getDisplayName(context: Context): String { fun Locale?.getDisplayName(context: Context): String = when (this) {
if (this == null) { null -> context.getString(R.string.all_languages)
return context.getString(R.string.various_languages) Locale.ROOT -> context.getString(R.string.various_languages)
} else -> getDisplayLanguage(this).toTitleCase(this)
return getDisplayLanguage(this).toTitleCase(this)
} }
private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> { private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {

@ -67,6 +67,7 @@ class MangaSourcesRepository @Inject constructor(
excludeBroken: Boolean, excludeBroken: Boolean,
types: Set<ContentType>, types: Set<ContentType>,
query: String?, query: String?,
locale: String?,
sortOrder: SourcesSortOrder?, sortOrder: SourcesSortOrder?,
): List<MangaSource> { ): List<MangaSource> {
assimilateNewSources() assimilateNewSources()
@ -81,6 +82,9 @@ class MangaSourcesRepository @Inject constructor(
skipNsfwSources = settings.isNsfwContentDisabled, skipNsfwSources = settings.isNsfwContentDisabled,
sortOrder = sortOrder, sortOrder = sortOrder,
) )
if (locale != null) {
sources.retainAll { it.locale == locale }
}
if (excludeBroken) { if (excludeBroken) {
sources.removeAll { it.isBroken } sources.removeAll { it.isBroken }
} }
@ -175,7 +179,7 @@ class MangaSourcesRepository @Inject constructor(
fun observeHasNewSources(): Flow<Boolean> = observeIsNsfwDisabled().map { skipNsfw -> fun observeHasNewSources(): Flow<Boolean> = observeIsNsfwDisabled().map { skipNsfw ->
val sources = dao.findAllFromVersion(BuildConfig.VERSION_CODE).toSources(skipNsfw, null) val sources = dao.findAllFromVersion(BuildConfig.VERSION_CODE).toSources(skipNsfw, null)
sources.isNotEmpty() sources.isNotEmpty() && sources.size != remoteSources.size
}.onStart { assimilateNewSources() } }.onStart { assimilateNewSources() }
fun observeHasNewSourcesForBadge(): Flow<Boolean> = combine( fun observeHasNewSourcesForBadge(): Flow<Boolean> = combine(

@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.parentView import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.showDistinct import org.koitharu.kotatsu.core.util.ext.showDistinct
@ -29,7 +30,6 @@ import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale import java.util.Locale
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -122,10 +122,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
b.spinnerLocale.context, b.spinnerLocale.context,
android.R.layout.simple_spinner_dropdown_item, android.R.layout.simple_spinner_dropdown_item,
android.R.id.text1, android.R.id.text1,
value.availableItems.map { value.availableItems.map { it.getDisplayName(b.spinnerLocale.context) },
it?.getDisplayLanguage(it)?.toTitleCase(it)
?: b.spinnerLocale.context.getString(R.string.various_languages)
},
) )
val selectedIndex = value.availableItems.indexOf(selected) val selectedIndex = value.availableItems.indexOf(selected)
if (selectedIndex >= 0) { if (selectedIndex >= 0) {

@ -18,13 +18,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.showDistinct import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.databinding.SheetWelcomeBinding import org.koitharu.kotatsu.databinding.SheetWelcomeBinding
import org.koitharu.kotatsu.filter.ui.model.FilterProperty import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
import java.util.Locale import java.util.Locale
@ -58,7 +58,7 @@ class WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipC
override fun onChipClick(chip: Chip, data: Any?) { override fun onChipClick(chip: Chip, data: Any?) {
when (data) { when (data) {
is ContentType -> viewModel.setTypeChecked(data, chip.isChecked) is ContentType -> viewModel.setTypeChecked(data, chip.isChecked)
is Locale? -> viewModel.setLocaleChecked(data, chip.isChecked) is Locale -> viewModel.setLocaleChecked(data, chip.isChecked)
} }
} }
@ -86,12 +86,12 @@ class WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipC
} }
} }
private fun onLocalesChanged(value: FilterProperty<Locale?>) { private fun onLocalesChanged(value: FilterProperty<Locale>) {
val chips = viewBinding?.chipsLocales ?: return val chips = viewBinding?.chipsLocales ?: return
chips.setChips( chips.setChips(
value.availableItems.map { value.availableItems.map {
ChipsView.ChipModel( ChipsView.ChipModel(
title = it?.getDisplayLanguage(it)?.toTitleCase(it) ?: getString(R.string.various_languages), title = it.getDisplayName(chips.context),
isCheckable = true, isCheckable = true,
isChecked = it in value.selectedItems, isChecked = it in value.selectedItems,
data = it, data = it,

@ -11,6 +11,7 @@ import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.LocaleComparator import org.koitharu.kotatsu.core.util.LocaleComparator
import org.koitharu.kotatsu.core.util.ext.sortedWithSafe import org.koitharu.kotatsu.core.util.ext.sortedWithSafe
import org.koitharu.kotatsu.core.util.ext.toList import org.koitharu.kotatsu.core.util.ext.toList
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.filter.ui.model.FilterProperty import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
@ -27,14 +28,14 @@ class WelcomeViewModel @Inject constructor(
) : BaseViewModel() { ) : BaseViewModel() {
private val allSources = repository.allMangaSources private val allSources = repository.allMangaSources
private val localesGroups by lazy { allSources.groupBy { it.locale?.let { x -> Locale(x) } } } private val localesGroups by lazy { allSources.groupBy { it.locale.toLocale() } }
private var updateJob: Job private var updateJob: Job
val locales = MutableStateFlow( val locales = MutableStateFlow(
FilterProperty<Locale?>( FilterProperty<Locale>(
availableItems = listOf(null), availableItems = listOf(Locale.ROOT),
selectedItems = setOf(null), selectedItems = setOf(Locale.ROOT),
isLoading = true, isLoading = true,
error = null, error = null,
), ),
@ -51,13 +52,14 @@ class WelcomeViewModel @Inject constructor(
init { init {
updateJob = launchJob(Dispatchers.Default) { updateJob = launchJob(Dispatchers.Default) {
val languages = localesGroups.keys.associateBy { x -> x?.language } val languages = localesGroups.keys.associateBy { x -> x.language }
val selectedLocales = HashSet<Locale?>(2) val selectedLocales = HashSet<Locale>(2)
selectedLocales += ConfigurationCompat.getLocales(context.resources.configuration).toList() ConfigurationCompat.getLocales(context.resources.configuration).toList()
.firstNotNullOfOrNull { lc -> languages[lc.language] } .firstNotNullOfOrNull { lc -> languages[lc.language] }
selectedLocales += null ?.let { selectedLocales += it }
selectedLocales += Locale.ROOT
locales.value = locales.value.copy( locales.value = locales.value.copy(
availableItems = localesGroups.keys.sortedWithSafe(nullsFirst(LocaleComparator())), availableItems = localesGroups.keys.sortedWithSafe(LocaleComparator()),
selectedItems = selectedLocales, selectedItems = selectedLocales,
isLoading = false, isLoading = false,
) )
@ -66,7 +68,7 @@ class WelcomeViewModel @Inject constructor(
} }
} }
fun setLocaleChecked(locale: Locale?, isChecked: Boolean) { fun setLocaleChecked(locale: Locale, isChecked: Boolean) {
val snapshot = locales.value val snapshot = locales.value
locales.value = snapshot.copy( locales.value = snapshot.copy(
selectedItems = if (isChecked) { selectedItems = if (isChecked) {
@ -99,7 +101,7 @@ class WelcomeViewModel @Inject constructor(
} }
private suspend fun commit() { private suspend fun commit() {
val languages = locales.value.selectedItems.mapToSet { it?.language } val languages = locales.value.selectedItems.mapToSet { it.language }
val types = types.value.selectedItems val types = types.value.selectedItems
val enabledSources = allSources.filterTo(EnumSet.noneOf(MangaSource::class.java)) { x -> val enabledSources = allSources.filterTo(EnumSet.noneOf(MangaSource::class.java)) { x ->
x.contentType in types && x.locale in languages x.contentType in types && x.locale in languages

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.settings.sources.catalog package org.koitharu.kotatsu.settings.sources.catalog
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -15,6 +16,7 @@ import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
@ -22,12 +24,13 @@ import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemCatalogPageBinding import org.koitharu.kotatsu.databinding.ItemCatalogPageBinding
import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding
import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
fun sourceCatalogItemSourceAD( fun sourceCatalogItemSourceAD(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
listener: OnListItemClickListener<SourceCatalogItem.Source> listener: OnListItemClickListener<SourceCatalogItem.Source>
) = adapterDelegateViewBinding<SourceCatalogItem.Source, SourceCatalogItem, ItemSourceCatalogBinding>( ) = adapterDelegateViewBinding<SourceCatalogItem.Source, ListModel, ItemSourceCatalogBinding>(
{ layoutInflater, parent -> { layoutInflater, parent ->
ItemSourceCatalogBinding.inflate(layoutInflater, parent, false) ItemSourceCatalogBinding.inflate(layoutInflater, parent, false)
}, },
@ -43,6 +46,11 @@ fun sourceCatalogItemSourceAD(
bind { bind {
binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewDescription.text = item.source.getSummary(context) binding.textViewDescription.text = item.source.getSummary(context)
binding.textViewDescription.drawableStart = if (item.source.isBroken) {
ContextCompat.getDrawable(context, R.drawable.ic_off_small)
} else {
null
}
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
crossfade(context) crossfade(context)
@ -59,7 +67,7 @@ fun sourceCatalogItemSourceAD(
fun sourceCatalogItemHintAD( fun sourceCatalogItemHintAD(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<SourceCatalogItem.Hint, SourceCatalogItem, ItemEmptyHintBinding>( ) = adapterDelegateViewBinding<SourceCatalogItem.Hint, ListModel, ItemEmptyHintBinding>(
{ inflater, parent -> ItemEmptyHintBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemEmptyHintBinding.inflate(inflater, parent, false) },
) { ) {

@ -73,6 +73,9 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
) )
viewBinding.recyclerView.updatePadding(
bottom = insets.bottom,
)
} }
override fun onChipClick(chip: Chip, data: Any?) { override fun onChipClick(chip: Chip, data: Any?) {

@ -7,16 +7,19 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
class SourcesCatalogAdapter( class SourcesCatalogAdapter(
listener: OnListItemClickListener<SourceCatalogItem.Source>, listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceCatalogItem>(), FastScroller.SectionIndexer { ) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init { init {
addDelegate(ListItemType.CHAPTER_LIST, sourceCatalogItemSourceAD(coil, lifecycleOwner, listener)) addDelegate(ListItemType.CHAPTER_LIST, sourceCatalogItemSourceAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.HINT_EMPTY, sourceCatalogItemHintAD(coil, lifecycleOwner)) addDelegate(ListItemType.HINT_EMPTY, sourceCatalogItemHintAD(coil, lifecycleOwner))
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
} }
override fun getSectionText(context: Context, position: Int): CharSequence? { override fun getSectionText(context: Context, position: Int): CharSequence? {

@ -1,25 +0,0 @@
package org.koitharu.kotatsu.settings.sources.catalog
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
class SourcesCatalogPagerAdapter(
listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceCatalogPage>(), TabLayoutMediator.TabConfigurationStrategy {
init {
delegatesManager.addDelegate(sourceCatalogPageAD(listener, coil, lifecycleOwner))
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = items.getOrNull(position) ?: return
tab.setText(item.type.titleResId)
}
}

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.settings.sources.catalog package org.koitharu.kotatsu.settings.sources.catalog
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.room.invalidationTrackerFlow
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -10,6 +11,8 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_SOURCES
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.ui.util.ReversibleAction
@ -17,9 +20,10 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.data.SourcesSortOrder import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.util.EnumSet import java.util.EnumSet
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -27,11 +31,14 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SourcesCatalogViewModel @Inject constructor( class SourcesCatalogViewModel @Inject constructor(
private val repository: MangaSourcesRepository, private val repository: MangaSourcesRepository,
private val settings: AppSettings, db: MangaDatabase,
settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val locales = repository.allMangaSources.mapToSet { it.locale } val locales: Set<String?> = repository.allMangaSources.mapTo(HashSet<String?>()) { it.locale }.also {
it.add(null)
}
private val searchQuery = MutableStateFlow<String?>(null) private val searchQuery = MutableStateFlow<String?>(null)
val appliedFilter = MutableStateFlow( val appliedFilter = MutableStateFlow(
@ -47,12 +54,13 @@ class SourcesCatalogViewModel @Inject constructor(
val hasNewSources = repository.observeHasNewSources() val hasNewSources = repository.observeHasNewSources()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
val content: StateFlow<List<SourceCatalogItem>> = combine( val content: StateFlow<List<ListModel>> = combine(
searchQuery, searchQuery,
appliedFilter, appliedFilter,
) { q, f -> db.invalidationTrackerFlow(TABLE_SOURCES),
) { q, f, _ ->
buildSourcesList(f, q) buildSourcesList(f, q)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
init { init {
repository.clearNewSourcesBadge() repository.clearNewSourcesBadge()
@ -96,8 +104,9 @@ class SourcesCatalogViewModel @Inject constructor(
excludeBroken = false, excludeBroken = false,
types = filter.types, types = filter.types,
query = query, query = query,
locale = filter.locale,
sortOrder = SourcesSortOrder.ALPHABETIC, sortOrder = SourcesSortOrder.ALPHABETIC,
).filter { it.locale == filter.locale } )
return if (sources.isEmpty()) { return if (sources.isEmpty()) {
listOf( listOf(
if (query == null) { if (query == null) {
@ -115,7 +124,9 @@ class SourcesCatalogViewModel @Inject constructor(
}, },
) )
} else { } else {
sources.map { sources.sortedBy {
it.isBroken
}.map {
SourceCatalogItem.Source(source = it) SourceCatalogItem.Source(source = it)
} }
} }

@ -0,0 +1,13 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M22.11 21.46L2.39 1.73L1.11 3L6.25 8.14C6.1 8.41 6 8.7 6 9V14.5L9.5 18V21H14.5V18L15.31 17.2L20.84 22.73L22.11 21.46M13.09 16.59L12.67 17H11.33L10.92 16.59L8 13.67V9.89L13.89 15.78L13.09 16.59M12.2 9L10.2 7H14V3H16V7C17 7 18 8 18 9V14.5L17.85 14.65L16 12.8V9.09C16 9.06 15.95 9 15.92 9H12.2M10 6.8L8 4.8V3H10V6.8Z" />
</vector>

@ -17,7 +17,8 @@
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar" android:id="@id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/scrollView_chips" android:id="@+id/scrollView_chips"
@ -43,11 +44,11 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />

@ -45,9 +45,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:drawablePadding="4dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center_vertical"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceBodySmall"
tools:drawableStart="@drawable/ic_off_small"
tools:text="English" /> tools:text="English" />
</LinearLayout> </LinearLayout>

@ -652,4 +652,5 @@
<string name="tracker_debug_info_summary">Debug information about background checks for new chapters</string> <string name="tracker_debug_info_summary">Debug information about background checks for new chapters</string>
<!-- In plural, used for filter --> <!-- In plural, used for filter -->
<string name="_new">New</string> <string name="_new">New</string>
<string name="all_languages">All languages</string>
</resources> </resources>

Loading…
Cancel
Save