Improve alternatives search functionality
This commit is contained in:
@@ -7,6 +7,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
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.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
@@ -14,6 +15,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
|||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.search.domain.SearchKind
|
import org.koitharu.kotatsu.search.domain.SearchKind
|
||||||
import org.koitharu.kotatsu.search.domain.SearchV2Helper
|
import org.koitharu.kotatsu.search.domain.SearchV2Helper
|
||||||
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val MAX_PARALLELISM = 4
|
private const val MAX_PARALLELISM = 4
|
||||||
@@ -24,8 +26,8 @@ class AlternativesUseCase @Inject constructor(
|
|||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(manga: Manga): Flow<Manga> {
|
suspend operator fun invoke(manga: Manga, throughDisabledSources: Boolean): Flow<Manga> {
|
||||||
val sources = getSources(manga.source)
|
val sources = getSources(manga.source, throughDisabledSources)
|
||||||
if (sources.isEmpty()) {
|
if (sources.isEmpty()) {
|
||||||
return emptyFlow()
|
return emptyFlow()
|
||||||
}
|
}
|
||||||
@@ -39,12 +41,14 @@ class AlternativesUseCase @Inject constructor(
|
|||||||
searchHelper(manga.title, SearchKind.TITLE)?.manga
|
searchHelper(manga.title, SearchKind.TITLE)?.manga
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
list?.forEach {
|
list?.forEach { m ->
|
||||||
launch {
|
if (m.id != manga.id) {
|
||||||
val details = runCatchingCancellable {
|
launch {
|
||||||
mangaRepositoryFactory.create(it.source).getDetails(it)
|
val details = runCatchingCancellable {
|
||||||
}.getOrDefault(it)
|
mangaRepositoryFactory.create(m.source).getDetails(m)
|
||||||
send(details)
|
}.getOrDefault(m)
|
||||||
|
send(details)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,19 +56,23 @@ class AlternativesUseCase @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getSources(ref: MangaSource): List<MangaSource> {
|
private suspend fun getSources(ref: MangaSource, disabled: Boolean): List<MangaSource> = if (disabled) {
|
||||||
val result = ArrayList<MangaSource>(MangaParserSource.entries.size - 2)
|
sourcesRepository.getDisabledSources()
|
||||||
result.addAll(sourcesRepository.getEnabledSources())
|
} else {
|
||||||
result.sortByDescending { it.priority(ref) }
|
sourcesRepository.getEnabledSources()
|
||||||
result.addAll(sourcesRepository.getDisabledSources().sortedByDescending { it.priority(ref) })
|
}.sortedByDescending { it.priority(ref) }
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MangaSource.priority(ref: MangaSource): Int {
|
private fun MangaSource.priority(ref: MangaSource): Int {
|
||||||
var res = 0
|
var res = 0
|
||||||
if (this is MangaParserSource && ref is MangaParserSource) {
|
if (this is MangaParserSource && ref is MangaParserSource) {
|
||||||
if (locale == ref.locale) res += 2
|
if (locale == ref.locale) {
|
||||||
if (contentType == ref.contentType) res++
|
res += 4
|
||||||
|
} else if (locale.toLocale() == Locale.getDefault()) {
|
||||||
|
res += 2
|
||||||
|
}
|
||||||
|
if (contentType == ref.contentType) {
|
||||||
|
res++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.model.chaptersCount
|
|||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.concat
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -35,7 +36,8 @@ class AutoFixUseCase @Inject constructor(
|
|||||||
if (seed.isHealthy()) {
|
if (seed.isHealthy()) {
|
||||||
return seed to null // no fix required
|
return seed to null // no fix required
|
||||||
}
|
}
|
||||||
val replacement = alternativesUseCase(seed)
|
val replacement = alternativesUseCase(seed, throughDisabledSources = false)
|
||||||
|
.concat(alternativesUseCase(seed, throughDisabledSources = true))
|
||||||
.filter { it.isHealthy() }
|
.filter { it.isHealthy() }
|
||||||
.runningFold<Manga, Manga?>(null) { best, candidate ->
|
.runningFold<Manga, Manga?>(null) { best, candidate ->
|
||||||
if (best == null || best < candidate) {
|
if (best == null || best < candidate) {
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
|
|||||||
import org.koitharu.kotatsu.core.util.ext.systemBarsInsets
|
import org.koitharu.kotatsu.core.util.ext.systemBarsInsets
|
||||||
import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding
|
import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.buttonFooterAD
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
|
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
|
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
||||||
@@ -32,6 +34,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
||||||
|
ListStateHolderListener,
|
||||||
OnListItemClickListener<MangaAlternativeModel> {
|
OnListItemClickListener<MangaAlternativeModel> {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -51,6 +54,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
|||||||
.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null))
|
.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null))
|
||||||
.addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
|
.addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
|
||||||
.addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
|
.addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
|
||||||
|
.addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(this))
|
||||||
with(viewBinding.recyclerView) {
|
with(viewBinding.recyclerView) {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
addItemDecoration(TypedListSpacingDecoration(context, addHorizontalPadding = false))
|
addItemDecoration(TypedListSpacingDecoration(context, addHorizontalPadding = false))
|
||||||
@@ -58,7 +62,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
|
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
|
||||||
viewModel.content.observe(this, listAdapter)
|
viewModel.list.observe(this, listAdapter)
|
||||||
viewModel.onMigrated.observeEvent(this) {
|
viewModel.onMigrated.observeEvent(this) {
|
||||||
Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()
|
||||||
router.openDetails(it)
|
router.openDetails(it)
|
||||||
@@ -92,6 +96,12 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRetryClick(error: Throwable) = viewModel.retry()
|
||||||
|
|
||||||
|
override fun onEmptyActionClick() = Unit
|
||||||
|
|
||||||
|
override fun onFooterButtonClick() = viewModel.continueSearch()
|
||||||
|
|
||||||
private fun confirmMigration(target: Manga) {
|
private fun confirmMigration(target: Manga) {
|
||||||
buildAlertDialog(this, isCentered = true) {
|
buildAlertDialog(this, isCentered = true) {
|
||||||
setIcon(R.drawable.ic_replace)
|
setIcon(R.drawable.ic_replace)
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package org.koitharu.kotatsu.alternatives.ui
|
package org.koitharu.kotatsu.alternatives.ui
|
||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.onEmpty
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.runningFold
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase
|
import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase
|
||||||
import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase
|
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.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
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.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
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.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -41,39 +48,62 @@ class AlternativesViewModel @Inject constructor(
|
|||||||
|
|
||||||
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
|
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
|
||||||
|
|
||||||
val onMigrated = MutableEventFlow<Manga>()
|
private var includeDisabledSources = MutableStateFlow(false)
|
||||||
val content = MutableStateFlow<List<ListModel>>(listOf(LoadingState))
|
private val results = MutableStateFlow<List<MangaAlternativeModel>>(emptyList())
|
||||||
|
|
||||||
private var migrationJob: Job? = null
|
private var migrationJob: Job? = null
|
||||||
|
private var searchJob: Job? = null
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
loading -> list + LoadingFooter()
|
||||||
|
includeDisabled -> list
|
||||||
|
else -> list + ButtonFooter(R.string.search_disabled_sources)
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchJob(Dispatchers.Default) {
|
doSearch(throughDisabledSources = false)
|
||||||
val ref = runCatchingCancellable {
|
}
|
||||||
mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
|
||||||
}.getOrDefault(manga)
|
fun retry() {
|
||||||
val refCount = ref.chaptersCount()
|
searchJob?.cancel()
|
||||||
alternativesUseCase(ref)
|
results.value = emptyList()
|
||||||
.map {
|
includeDisabledSources.value = false
|
||||||
MangaAlternativeModel(
|
doSearch(throughDisabledSources = false)
|
||||||
mangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,
|
}
|
||||||
referenceChapters = refCount,
|
|
||||||
)
|
fun continueSearch() {
|
||||||
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
|
if (includeDisabledSources.value) {
|
||||||
acc.filterIsInstance<MangaAlternativeModel>() + item + LoadingFooter()
|
return
|
||||||
}.onEmpty {
|
}
|
||||||
emit(
|
val prevJob = searchJob
|
||||||
listOf(
|
searchJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
EmptyState(
|
includeDisabledSources.value = true
|
||||||
icon = R.drawable.ic_empty_common,
|
prevJob?.join()
|
||||||
textPrimary = R.string.nothing_found,
|
doSearch(throughDisabledSources = true)
|
||||||
textSecondary = R.string.text_search_holder_secondary,
|
|
||||||
actionStringRes = 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}.collect {
|
|
||||||
content.value = it
|
|
||||||
}
|
|
||||||
content.value = content.value.filterNot { it is LoadingFooter }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,4 +116,21 @@ class AlternativesViewModel @Inject constructor(
|
|||||||
onMigrated.call(target)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import android.os.SystemClock
|
|||||||
import kotlinx.coroutines.channels.SendChannel
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
@@ -18,6 +20,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.SuspendLazy
|
import org.koitharu.kotatsu.parsers.util.suspendlazy.SuspendLazy
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -142,3 +145,12 @@ suspend fun <T> SendChannel<T>.sendNotNull(item: T?) {
|
|||||||
send(item)
|
send(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> MutableStateFlow<List<T>>.append(item: T) {
|
||||||
|
update { list -> list + item }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Flow<T>.concat(other: Flow<T>) = flow {
|
||||||
|
emitAll(this@concat)
|
||||||
|
emitAll(other)
|
||||||
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ class DetailsActivity :
|
|||||||
viewBinding.chipsTags.onChipClickListener = this
|
viewBinding.chipsTags.onChipClickListener = this
|
||||||
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
||||||
viewBinding.containerBottomSheet?.let { sheet ->
|
viewBinding.containerBottomSheet?.let { sheet ->
|
||||||
|
sheet.setOnClickListener(this)
|
||||||
sheet.addOnLayoutChangeListener(this)
|
sheet.addOnLayoutChangeListener(this)
|
||||||
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
|
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
|
||||||
BottomSheetBehavior.from(sheet).addBottomSheetCallback(
|
BottomSheetBehavior.from(sheet).addBottomSheetCallback(
|
||||||
|
|||||||
@@ -341,7 +341,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
viewBinding.fab?.isEnabled = !isLoading
|
val fab = viewBinding.fab ?: viewBinding.navRail?.headerView ?: return
|
||||||
|
fab.isEnabled = !isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onResumeEnabledChanged(isEnabled: Boolean) {
|
private fun onResumeEnabledChanged(isEnabled: Boolean) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
@@ -25,6 +24,7 @@ import org.koitharu.kotatsu.core.model.UnknownMangaSource
|
|||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.append
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.toLocale
|
import org.koitharu.kotatsu.core.util.ext.toLocale
|
||||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
@@ -284,7 +284,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun appendResult(item: SearchResultsListModel?) {
|
private fun appendResult(item: SearchResultsListModel?) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
results.update { list -> list + item }
|
results.append(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ material = "1.13.0-alpha11"
|
|||||||
moshi = "1.15.2"
|
moshi = "1.15.2"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
okio = "3.10.2"
|
okio = "3.10.2"
|
||||||
parsers = "77a5216ebf"
|
parsers = "d5a4cf68c6"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
recyclerview = "1.4.0"
|
recyclerview = "1.4.0"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user