|
|
|
|
@ -1,13 +1,17 @@
|
|
|
|
|
package org.koitharu.kotatsu.alternatives.ui
|
|
|
|
|
|
|
|
|
|
import androidx.lifecycle.SavedStateHandle
|
|
|
|
|
import androidx.lifecycle.viewModelScope
|
|
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
|
import kotlinx.coroutines.cancelAndJoin
|
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.map
|
|
|
|
|
import kotlinx.coroutines.flow.onEmpty
|
|
|
|
|
import kotlinx.coroutines.flow.runningFold
|
|
|
|
|
import kotlinx.coroutines.flow.SharingStarted
|
|
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.combine
|
|
|
|
|
import kotlinx.coroutines.flow.stateIn
|
|
|
|
|
import kotlinx.coroutines.plus
|
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
|
import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase
|
|
|
|
|
import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase
|
|
|
|
|
@ -18,16 +22,19 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
|
|
|
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
|
|
|
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.append
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.call
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.require
|
|
|
|
|
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.ButtonFooter
|
|
|
|
|
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.MangaGridModel
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrDefault
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@HiltViewModel
|
|
|
|
|
@ -41,39 +48,62 @@ class AlternativesViewModel @Inject constructor(
|
|
|
|
|
|
|
|
|
|
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
|
|
|
|
|
|
|
|
|
|
val onMigrated = MutableEventFlow<Manga>()
|
|
|
|
|
val content = MutableStateFlow<List<ListModel>>(listOf(LoadingState))
|
|
|
|
|
private var includeDisabledSources = MutableStateFlow(false)
|
|
|
|
|
private val results = MutableStateFlow<List<MangaAlternativeModel>>(emptyList())
|
|
|
|
|
|
|
|
|
|
private var migrationJob: Job? = null
|
|
|
|
|
private var searchJob: Job? = null
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
launchJob(Dispatchers.Default) {
|
|
|
|
|
val ref = runCatchingCancellable {
|
|
|
|
|
mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
|
|
|
|
}.getOrDefault(manga)
|
|
|
|
|
val refCount = ref.chaptersCount()
|
|
|
|
|
alternativesUseCase(ref)
|
|
|
|
|
.map {
|
|
|
|
|
MangaAlternativeModel(
|
|
|
|
|
mangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,
|
|
|
|
|
referenceChapters = refCount,
|
|
|
|
|
)
|
|
|
|
|
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
|
|
|
|
|
acc.filterIsInstance<MangaAlternativeModel>() + item + LoadingFooter()
|
|
|
|
|
}.onEmpty {
|
|
|
|
|
emit(
|
|
|
|
|
listOf(
|
|
|
|
|
EmptyState(
|
|
|
|
|
icon = R.drawable.ic_empty_common,
|
|
|
|
|
textPrimary = R.string.nothing_found,
|
|
|
|
|
textSecondary = R.string.text_search_holder_secondary,
|
|
|
|
|
actionStringRes = 0,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
private val mangaDetails = suspendLazy {
|
|
|
|
|
mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val onMigrated = MutableEventFlow<Manga>()
|
|
|
|
|
|
|
|
|
|
val list: StateFlow<List<ListModel>> = combine(
|
|
|
|
|
results,
|
|
|
|
|
isLoading,
|
|
|
|
|
includeDisabledSources,
|
|
|
|
|
) { list, loading, includeDisabled ->
|
|
|
|
|
when {
|
|
|
|
|
list.isEmpty() -> listOf(
|
|
|
|
|
when {
|
|
|
|
|
loading -> LoadingState
|
|
|
|
|
else -> EmptyState(
|
|
|
|
|
icon = R.drawable.ic_empty_common,
|
|
|
|
|
textPrimary = R.string.nothing_found,
|
|
|
|
|
textSecondary = R.string.text_search_holder_secondary,
|
|
|
|
|
actionStringRes = 0,
|
|
|
|
|
)
|
|
|
|
|
}.collect {
|
|
|
|
|
content.value = it
|
|
|
|
|
}
|
|
|
|
|
content.value = content.value.filterNot { it is LoadingFooter }
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
loading -> list + LoadingFooter()
|
|
|
|
|
includeDisabled -> list
|
|
|
|
|
else -> list + ButtonFooter(R.string.search_disabled_sources)
|
|
|
|
|
}
|
|
|
|
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
doSearch(throughDisabledSources = false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun retry() {
|
|
|
|
|
searchJob?.cancel()
|
|
|
|
|
results.value = emptyList()
|
|
|
|
|
includeDisabledSources.value = false
|
|
|
|
|
doSearch(throughDisabledSources = false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun continueSearch() {
|
|
|
|
|
if (includeDisabledSources.value) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
val prevJob = searchJob
|
|
|
|
|
searchJob = launchLoadingJob(Dispatchers.Default) {
|
|
|
|
|
includeDisabledSources.value = true
|
|
|
|
|
prevJob?.join()
|
|
|
|
|
doSearch(throughDisabledSources = true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -86,4 +116,21 @@ class AlternativesViewModel @Inject constructor(
|
|
|
|
|
onMigrated.call(target)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun doSearch(throughDisabledSources: Boolean) {
|
|
|
|
|
val prevJob = searchJob
|
|
|
|
|
searchJob = launchLoadingJob(Dispatchers.Default) {
|
|
|
|
|
prevJob?.cancelAndJoin()
|
|
|
|
|
val ref = mangaDetails.getOrDefault(manga)
|
|
|
|
|
val refCount = ref.chaptersCount()
|
|
|
|
|
alternativesUseCase.invoke(ref, throughDisabledSources)
|
|
|
|
|
.collect {
|
|
|
|
|
val model = MangaAlternativeModel(
|
|
|
|
|
mangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,
|
|
|
|
|
referenceChapters = refCount,
|
|
|
|
|
)
|
|
|
|
|
results.append(model)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|