Refactor manga list model mapping

This commit is contained in:
Koitharu
2024-07-22 14:58:55 +03:00
parent c14d39c456
commit 607785dcd4
32 changed files with 255 additions and 283 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@
.externalNativeBuild
.cxx
/.idea/deviceManager.xml
/.kotlin/

View File

@@ -15,11 +15,13 @@ import org.koitharu.kotatsu.core.model.chaptersCount
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
@@ -34,7 +36,8 @@ class AlternativesViewModel @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val alternativesUseCase: AlternativesUseCase,
private val migrateUseCase: MigrateUseCase,
private val extraProvider: ListExtraProvider,
private val historyRepository: HistoryRepository,
private val settings: AppSettings,
) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
@@ -53,7 +56,7 @@ class AlternativesViewModel @Inject constructor(
.map {
MangaAlternativeModel(
manga = it,
progress = extraProvider.getProgress(it.id),
progress = getProgress(it.id),
referenceChapters = refCount,
)
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
@@ -86,13 +89,11 @@ class AlternativesViewModel @Inject constructor(
}
}
private suspend fun mapList(list: List<Manga>, refCount: Int): List<MangaAlternativeModel> {
return list.map {
MangaAlternativeModel(
manga = it,
progress = extraProvider.getProgress(it.id),
referenceChapters = refCount,
)
private suspend fun getProgress(mangaId: Long): Float {
return if (settings.isReadingIndicatorsEnabled) {
historyRepository.getProgress(mangaId)
} else {
PROGRESS_NONE
}
}
}

View File

@@ -89,11 +89,11 @@ import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga
@@ -122,7 +122,7 @@ class DetailsActivity :
lateinit var coil: ImageLoader
@Inject
lateinit var tagHighlighter: ListExtraProvider
lateinit var listMapper: MangaListMapper
private val viewModel: DetailsViewModel by viewModels()
private lateinit var menuProvider: DetailsMenuProvider
@@ -391,7 +391,7 @@ class DetailsActivity :
}
}
private fun onRelatedMangaChanged(related: List<MangaItemModel>) {
private fun onRelatedMangaChanged(related: List<MangaListModel>) {
if (related.isEmpty()) {
viewBinding.groupRelated.isVisible = false
return
@@ -613,15 +613,7 @@ class DetailsActivity :
private fun bindTags(manga: Manga) {
viewBinding.chipsTags.isVisible = manga.tags.isNotEmpty()
viewBinding.chipsTags.setChips(
manga.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
tint = tagHighlighter.getTagTint(tag),
data = tag,
)
},
)
viewBinding.chipsTags.setChips(listMapper.mapTags(manga.tags))
}
private fun loadCover(manga: Manga) {

View File

@@ -50,9 +50,8 @@ import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.model.MangaBranch
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga
@@ -76,7 +75,7 @@ class DetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
private val relatedMangaUseCase: RelatedMangaUseCase,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
private val detailsLoadUseCase: DetailsLoadUseCase,
private val progressUpdateUseCase: ProgressUpdateUseCase,
private val readingTimeUseCase: ReadingTimeUseCase,
@@ -171,9 +170,12 @@ class DetailsViewModel @Inject constructor(
val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val relatedManga: StateFlow<List<MangaItemModel>> = manga.mapLatest {
val relatedManga: StateFlow<List<MangaListModel>> = manga.mapLatest {
if (it != null && settings.isRelatedMangaEnabled) {
relatedMangaUseCase.invoke(it)?.toUi(ListMode.GRID, extraProvider).orEmpty()
mangaListMapper.toListModelList(
manga = relatedMangaUseCase(it).orEmpty(),
mode = ListMode.GRID,
)
} else {
emptyList()
}

View File

@@ -20,12 +20,11 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
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 javax.inject.Inject
@@ -34,7 +33,7 @@ class RelatedListViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
mangaRepositoryFactory: MangaRepository.Factory,
settings: AppSettings,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
@@ -53,7 +52,7 @@ class RelatedListViewModel @Inject constructor(
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
list == null -> listOf(LoadingState)
list.isEmpty() -> listOf(createEmptyState())
else -> list.toUi(mode, extraProvider)
else -> mangaListMapper.toListModelList(list, mode)
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))

View File

@@ -33,7 +33,7 @@ import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -190,7 +190,7 @@ class ExploreViewModel @Inject constructor(
}
private fun List<Manga>.toRecommendationList() = map { manga ->
MangaListModel(
MangaCompactListModel(
id = manga.id,
title = manga.title,
subtitle = manga.tags.joinToString { it.title },

View File

@@ -34,7 +34,7 @@ import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
import org.koitharu.kotatsu.parsers.model.Manga
fun exploreButtonsAD(
@@ -66,7 +66,7 @@ fun exploreRecommendationItemAD(
{ layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) },
) {
val adapter = BaseListAdapter<MangaListModel>()
val adapter = BaseListAdapter<MangaCompactListModel>()
.addDelegate(ListItemType.MANGA_LIST, recommendationMangaItemAD(coil, itemClickListener, lifecycleOwner))
binding.pager.adapter = adapter
binding.pager.recyclerView?.isNestedScrollingEnabled = false
@@ -81,7 +81,7 @@ fun recommendationMangaItemAD(
coil: ImageLoader,
itemClickListener: OnListItemClickListener<Manga>,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<MangaListModel, MangaListModel, ItemRecommendationMangaBinding>(
) = adapterDelegateViewBinding<MangaCompactListModel, MangaCompactListModel, ItemRecommendationMangaBinding>(
{ layoutInflater, parent -> ItemRecommendationMangaBinding.inflate(layoutInflater, parent, false) },
) {

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.explore.ui.model
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
data class RecommendationsItem(
val manga: List<MangaListModel>
val manga: List<MangaCompactListModel>
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {

View File

@@ -24,13 +24,12 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
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 java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@@ -41,7 +40,7 @@ private const val PAGE_SIZE = 20
class FavouritesListViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: FavouritesRepository,
private val listExtraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
private val markAsReadUseCase: MarkAsReadUseCase,
settings: AppSettings,
downloadScheduler: DownloadWorker.Scheduler,
@@ -86,7 +85,7 @@ class FavouritesListViewModel @Inject constructor(
else -> {
isReady.set(true)
list.toUi(mode, listExtraProvider)
mangaListMapper.toListModelList(list, mode)
}
}
}.catch {

View File

@@ -28,8 +28,8 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -38,9 +38,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.TipModel
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
import org.koitharu.kotatsu.list.ui.model.toListModel
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import java.time.Instant
@@ -53,7 +50,7 @@ private const val PAGE_SIZE = 20
class HistoryListViewModel @Inject constructor(
private val repository: HistoryRepository,
settings: AppSettings,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
private val localMangaRepository: LocalMangaRepository,
private val markAsReadUseCase: MarkAsReadUseCase,
networkState: NetworkState,
@@ -203,11 +200,7 @@ class HistoryListViewModel @Inject constructor(
prevHeader = header
}
}
result += when (mode) {
ListMode.LIST -> manga.toListModel(extraProvider)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(extraProvider)
ListMode.GRID -> manga.toGridModel(extraProvider)
}
result += mangaListMapper.toListModel(manga, mode)
}
return result
}

View File

@@ -1,60 +0,0 @@
package org.koitharu.kotatsu.list.domain
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.core.prefs.AppSettings
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import javax.inject.Inject
@Reusable
class ListExtraProvider @Inject constructor(
@ApplicationContext context: Context,
private val settings: AppSettings,
private val trackingRepository: TrackingRepository,
private val historyRepository: HistoryRepository,
) {
private val dict by lazy {
context.resources.openRawResource(R.raw.tags_redlist).use {
val set = HashSet<String>()
it.bufferedReader().forEachLine { x ->
val line = x.trim()
if (line.isNotEmpty()) {
set.add(line)
}
}
set
}
}
suspend fun getCounter(mangaId: Long): Int {
return if (settings.isTrackerEnabled) {
trackingRepository.getNewChaptersCount(mangaId)
} else {
0
}
}
suspend fun getProgress(mangaId: Long): Float {
return if (settings.isReadingIndicatorsEnabled) {
historyRepository.getProgress(mangaId)
} else {
PROGRESS_NONE
}
}
@ColorRes
fun getTagTint(tag: MangaTag): Int {
return if (tag.title.lowercase() in dict) {
R.color.warning
} else {
0
}
}
}

View File

@@ -0,0 +1,129 @@
package org.koitharu.kotatsu.list.domain
import android.content.Context
import androidx.annotation.ColorRes
import androidx.collection.MutableScatterSet
import androidx.collection.ScatterSet
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
import org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MangaListMapper @Inject constructor(
@ApplicationContext context: Context,
private val settings: AppSettings,
private val trackingRepository: TrackingRepository,
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
) {
private val dict by lazy { readTagsDict(context) }
suspend fun toListModelList(manga: Collection<Manga>, mode: ListMode): List<MangaListModel> = manga.map {
toListModel(it, mode)
}
suspend fun toListModelList(
destination: MutableCollection<in MangaListModel>,
manga: Collection<Manga>,
mode: ListMode
) = manga.mapTo(destination) {
toListModel(it, mode)
}
suspend fun toListModel(manga: Manga, mode: ListMode): MangaListModel = when (mode) {
ListMode.LIST -> toCompactListModel(manga)
ListMode.DETAILED_LIST -> toDetailedListModel(manga)
ListMode.GRID -> toGridModel(manga)
}
suspend fun toCompactListModel(manga: Manga) = MangaCompactListModel(
id = manga.id,
title = manga.title,
subtitle = manga.tags.joinToString(", ") { it.title },
coverUrl = manga.coverUrl,
manga = manga,
counter = getCounter(manga.id),
progress = getProgress(manga.id),
)
suspend fun toDetailedListModel(manga: Manga) = MangaDetailedListModel(
id = manga.id,
title = manga.title,
subtitle = manga.altTitle,
coverUrl = manga.coverUrl,
manga = manga,
counter = getCounter(manga.id),
progress = getProgress(manga.id),
tags = mapTags(manga.tags),
)
suspend fun toGridModel(manga: Manga) = MangaGridModel(
id = manga.id,
title = manga.title,
coverUrl = manga.coverUrl,
manga = manga,
counter = getCounter(manga.id),
progress = getProgress(manga.id),
)
fun mapTags(tags: Collection<MangaTag>) = tags.map {
ChipsView.ChipModel(
tint = getTagTint(it),
title = it.title,
data = it,
)
}
private suspend fun getCounter(mangaId: Long): Int {
return if (settings.isTrackerEnabled) {
trackingRepository.getNewChaptersCount(mangaId)
} else {
0
}
}
private suspend fun getProgress(mangaId: Long): Float {
return if (settings.isReadingIndicatorsEnabled) {
historyRepository.getProgress(mangaId)
} else {
PROGRESS_NONE
}
}
@ColorRes
private fun getTagTint(tag: MangaTag): Int {
return if (tag.title.lowercase() in dict) {
R.color.warning
} else {
0
}
}
private fun readTagsDict(context: Context): ScatterSet<String> =
context.resources.openRawResource(R.raw.tags_redlist).use {
val set = MutableScatterSet<String>()
it.bufferedReader().forEachLine { x ->
val line = x.trim()
if (line.isNotEmpty()) {
set.add(line)
}
}
set.trim()
set
}
}

View File

@@ -20,7 +20,6 @@ import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -51,7 +50,7 @@ import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
@@ -281,7 +280,7 @@ abstract class MangaListFragment :
return when (item.itemId) {
R.id.action_select_all -> {
val ids = listAdapter?.items?.mapNotNull {
(it as? MangaItemModel)?.id
(it as? MangaListModel)?.id
} ?: return false
selectionController?.addAll(ids)
true
@@ -327,7 +326,7 @@ abstract class MangaListFragment :
val items = listAdapter?.items ?: return emptySet()
val result = ArraySet<Manga>(checkedIds.size)
for (item in items) {
if (item is MangaItemModel && item.id in checkedIds) {
if (item is MangaListModel && item.id in checkedIds) {
result.add(item.manga)
}
}

View File

@@ -14,7 +14,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.core.util.ext.getItem
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import com.google.android.material.R as materialR
open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
@@ -37,7 +37,7 @@ open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDec
override fun getItemId(parent: RecyclerView, child: View): Long {
val holder = parent.getChildViewHolder(child) ?: return NO_ID
val item = holder.getItem(MangaItemModel::class.java) ?: return NO_ID
val item = holder.getItem(MangaListModel::class.java) ?: return NO_ID
return item.id
}

View File

@@ -16,13 +16,13 @@ import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
import org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel
fun mangaListDetailedItemAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: MangaDetailsClickListener,
) = adapterDelegateViewBinding<MangaListDetailedModel, ListModel, ItemMangaListDetailsBinding>(
) = adapterDelegateViewBinding<MangaDetailedListModel, ListModel, ItemMangaListDetailsBinding>(
{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null

View File

@@ -15,14 +15,14 @@ import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
import org.koitharu.kotatsu.parsers.model.Manga
fun mangaListItemAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Manga>,
) = adapterDelegateViewBinding<MangaListModel, ListModel, ItemMangaListBinding>(
) = adapterDelegateViewBinding<MangaCompactListModel, ListModel, ItemMangaListBinding>(
{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null

View File

@@ -3,74 +3,8 @@ package org.koitharu.kotatsu.list.ui.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
import org.koitharu.kotatsu.core.util.ext.ifZero
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.parsers.model.Manga
suspend fun Manga.toListModel(
extraProvider: ListExtraProvider?
) = MangaListModel(
id = id,
title = title,
subtitle = tags.joinToString(", ") { it.title },
coverUrl = coverUrl,
manga = this,
counter = extraProvider?.getCounter(id) ?: 0,
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
)
suspend fun Manga.toListDetailedModel(
extraProvider: ListExtraProvider?,
) = MangaListDetailedModel(
id = id,
title = title,
subtitle = altTitle,
coverUrl = coverUrl,
manga = this,
counter = extraProvider?.getCounter(id) ?: 0,
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
tags = tags.map {
ChipsView.ChipModel(
tint = extraProvider?.getTagTint(it) ?: 0,
title = it.title,
data = it,
)
},
)
suspend fun Manga.toGridModel(
extraProvider: ListExtraProvider?,
) = MangaGridModel(
id = id,
title = title,
coverUrl = coverUrl,
manga = this,
counter = extraProvider?.getCounter(id) ?: 0,
progress = extraProvider?.getProgress(id) ?: PROGRESS_NONE,
)
suspend fun List<Manga>.toUi(
mode: ListMode,
extraProvider: ListExtraProvider,
): List<MangaItemModel> = if (isEmpty()) {
emptyList()
} else {
toUi(ArrayList(size), mode, extraProvider)
}
suspend fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
destination: C,
mode: ListMode,
extraProvider: ListExtraProvider,
): C = when (mode) {
ListMode.LIST -> mapTo(destination) { it.toListModel(extraProvider) }
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(extraProvider) }
ListMode.GRID -> mapTo(destination) { it.toGridModel(extraProvider) }
}
fun Throwable.toErrorState(canRetry: Boolean = true, @StringRes secondaryAction: Int = 0) = ErrorState(
exception = this,

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.parsers.model.Manga
data class MangaCompactListModel(
override val id: Long,
override val title: String,
val subtitle: String,
override val coverUrl: String,
override val manga: Manga,
override val counter: Int,
override val progress: Float,
) : MangaListModel()

View File

@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.parsers.model.Manga
data class MangaListDetailedModel(
data class MangaDetailedListModel(
override val id: Long,
override val title: String,
val subtitle: String?,
@@ -12,4 +12,4 @@ data class MangaListDetailedModel(
override val counter: Int,
override val progress: Float,
val tags: List<ChipsView.ChipModel>,
) : MangaItemModel()
) : MangaListModel()

View File

@@ -9,4 +9,4 @@ data class MangaGridModel(
override val manga: Manga,
override val counter: Int,
override val progress: Float,
) : MangaItemModel()
) : MangaListModel()

View File

@@ -1,31 +0,0 @@
package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
sealed class MangaItemModel : ListModel {
abstract val id: Long
abstract val manga: Manga
abstract val title: String
abstract val coverUrl: String
abstract val counter: Int
abstract val progress: Float
val source: MangaSource
get() = manga.source
override fun areItemsTheSame(other: ListModel): Boolean {
return other is MangaItemModel && other.javaClass == javaClass && id == other.id
}
override fun getChangePayload(previousState: ListModel): Any? {
return when {
previousState !is MangaItemModel -> super.getChangePayload(previousState)
progress != previousState.progress -> ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED
counter != previousState.counter -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED
else -> null
}
}
}

View File

@@ -1,13 +1,31 @@
package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
data class MangaListModel(
override val id: Long,
override val title: String,
val subtitle: String,
override val coverUrl: String,
override val manga: Manga,
override val counter: Int,
override val progress: Float,
) : MangaItemModel()
sealed class MangaListModel : ListModel {
abstract val id: Long
abstract val manga: Manga
abstract val title: String
abstract val coverUrl: String
abstract val counter: Int
abstract val progress: Float
val source: MangaSource
get() = manga.source
override fun areItemsTheSame(other: ListModel): Boolean {
return other is MangaListModel && other.javaClass == javaClass && id == other.id
}
override fun getChangePayload(previousState: ListModel): Any? {
return when {
previousState !is MangaListModel -> super.getChangePayload(previousState)
progress != previousState.progress -> ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED
counter != previousState.counter -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED
else -> null
}
}
}

View File

@@ -25,18 +25,17 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.sanitize
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import javax.inject.Inject
@HiltViewModel
class PreviewViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
private val repositoryFactory: MangaRepository.Factory,
private val historyRepository: HistoryRepository,
private val imageGetter: Html.ImageGetter,
@@ -81,13 +80,7 @@ class PreviewViewModel @Inject constructor(
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null)
val tagsChips = manga.map {
it.tags.map { tag ->
ChipsView.ChipModel(
title = tag.title,
tint = extraProvider.getTagTint(tag),
data = tag,
)
}
mangaListMapper.mapTags(it.tags)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
init {

View File

@@ -16,10 +16,10 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.model.TipModel
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.LocalStorageManager
@@ -35,7 +35,7 @@ class LocalListViewModel @Inject constructor(
filter: FilterCoordinator,
private val settings: AppSettings,
downloadScheduler: DownloadWorker.Scheduler,
listExtraProvider: ListExtraProvider,
mangaListMapper: MangaListMapper,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
exploreRepository: ExploreRepository,
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
@@ -46,7 +46,7 @@ class LocalListViewModel @Inject constructor(
mangaRepositoryFactory,
filter,
settings,
listExtraProvider,
mangaListMapper,
downloadScheduler,
exploreRepository,
sourcesRepository,
@@ -70,7 +70,7 @@ class LocalListViewModel @Inject constructor(
return
}
for (item in list) {
if (item !is MangaItemModel) {
if (item !is MangaListModel) {
continue
}
val file = item.manga.url.toUriOrNull()?.toFileOrNull() ?: continue

View File

@@ -32,7 +32,7 @@ import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.MangaFilter
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -40,7 +40,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
@@ -55,7 +54,7 @@ open class RemoteListViewModel @Inject constructor(
mangaRepositoryFactory: MangaRepository.Factory,
private val filter: FilterCoordinator,
settings: AppSettings,
listExtraProvider: ListExtraProvider,
mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler,
private val exploreRepository: ExploreRepository,
sourcesRepository: MangaSourcesRepository,
@@ -96,7 +95,7 @@ open class RemoteListViewModel @Inject constructor(
list == null -> add(LoadingState)
list.isEmpty() -> add(createEmptyState(canResetFilter = header.value.isFilterApplied))
else -> {
list.toUi(this, mode, listExtraProvider)
mangaListMapper.toListModelList(this, list, mode)
when {
error != null -> add(error.toErrorFooter())
hasNext -> add(LoadingFooter())

View File

@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -28,7 +28,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
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.parsers.model.MangaListFilter
import javax.inject.Inject
@@ -38,7 +37,7 @@ class SearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
repositoryFactory: MangaRepository.Factory,
settings: AppSettings,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
@@ -69,7 +68,7 @@ class SearchViewModel @Inject constructor(
else -> {
val result = ArrayList<ListModel>(list.size + 1)
list.toUi(result, mode, extraProvider)
mangaListMapper.toListModelList(result, list, mode)
when {
error != null -> result += error.toErrorFooter()
hasNext -> result += LoadingFooter()

View File

@@ -2,13 +2,13 @@ package org.koitharu.kotatsu.search.ui.multi
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.parsers.model.MangaSource
data class MultiSearchListModel(
val source: MangaSource,
val hasMore: Boolean,
val list: List<MangaItemModel>,
val list: List<MangaListModel>,
val error: Throwable?,
) : ListModel {

View File

@@ -29,12 +29,11 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
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.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -46,7 +45,7 @@ private const val MIN_HAS_MORE_ITEMS = 8
@HiltViewModel
class MultiSearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
private val mangaRepositoryFactory: MangaRepository.Factory,
private val downloadScheduler: DownloadWorker.Scheduler,
private val sourcesRepository: MangaSourcesRepository,
@@ -121,8 +120,10 @@ class MultiSearchViewModel @Inject constructor(
launch {
val item = runCatchingCancellable {
semaphore.withPermit {
repository.getList(offset = 0, filter = MangaListFilter.Search(q))
.toUi(ListMode.GRID, extraProvider)
mangaListMapper.toListModelList(
manga = repository.getList(offset = 0, filter = MangaListFilter.Search(q)),
mode = ListMode.GRID,
)
}
}.fold(
onSuccess = { list ->

View File

@@ -14,12 +14,11 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.util.ext.onFirst
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
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.suggestions.domain.SuggestionRepository
import javax.inject.Inject
@@ -27,7 +26,7 @@ import javax.inject.Inject
class SuggestionsViewModel @Inject constructor(
repository: SuggestionRepository,
settings: AppSettings,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler,
private val suggestionsScheduler: SuggestionsWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
@@ -49,7 +48,7 @@ class SuggestionsViewModel @Inject constructor(
),
)
else -> list.toUi(mode, extraProvider)
else -> mangaListMapper.toListModelList(list, mode)
}
}.onStart {
loadingCounter.increment()

View File

@@ -20,12 +20,11 @@ import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
@@ -42,7 +41,7 @@ class FeedViewModel @Inject constructor(
private val settings: AppSettings,
private val repository: TrackingRepository,
private val scheduler: TrackWorker.Scheduler,
private val listExtraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
) : BaseViewModel() {
private val limit = MutableStateFlow(PAGE_SIZE)
@@ -135,7 +134,7 @@ class FeedViewModel @Inject constructor(
null
} else {
UpdatedMangaHeader(
mangaList.map { it.manga.toGridModel(listExtraProvider) },
mangaList.map { mangaListMapper.toGridModel(it.manga) },
)
}
}

View File

@@ -2,10 +2,10 @@ package org.koitharu.kotatsu.tracker.ui.feed.model
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
data class UpdatedMangaHeader(
val list: List<MangaItemModel>,
val list: List<MangaListModel>,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {

View File

@@ -17,16 +17,13 @@ import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
import org.koitharu.kotatsu.core.util.ext.onFirst
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
import org.koitharu.kotatsu.list.ui.model.toListModel
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import javax.inject.Inject
@@ -35,7 +32,7 @@ import javax.inject.Inject
class UpdatesViewModel @Inject constructor(
private val repository: TrackingRepository,
settings: AppSettings,
private val extraProvider: ListExtraProvider,
private val mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
@@ -93,11 +90,7 @@ class UpdatesViewModel @Inject constructor(
prevHeader = header
}
}
result += when (mode) {
ListMode.LIST -> item.manga.toListModel(extraProvider)
ListMode.DETAILED_LIST -> item.manga.toListDetailedModel(extraProvider)
ListMode.GRID -> item.manga.toGridModel(extraProvider)
}
result += mangaListMapper.toListModel(item.manga, mode)
}
return result
}