Show reason why manga has no chapters

This commit is contained in:
Koitharu
2025-07-30 14:06:45 +03:00
parent fe21af5443
commit 1b76f21507
8 changed files with 64 additions and 27 deletions

View File

@@ -104,14 +104,14 @@ class CaptchaHandler @Inject constructor(
val dao = databaseProvider.get().getSourcesDao()
dao.setCfState(source.name, exception?.state ?: CloudFlareHelper.PROTECTION_NOT_DETECTED)
val exceptions = dao.findAllCaptchaRequired().mapNotNull {
it.source.toMangaSourceOrNull()
}.filterNot {
SourceSettings(context, it).isCaptchaNotificationsDisabled
}.mapNotNull {
exceptionMap[it]
}
if (notify && context.checkNotificationPermission(CHANNEL_ID)) {
val exceptions = dao.findAllCaptchaRequired().mapNotNull {
it.source.toMangaSourceOrNull()
}.filterNot {
SourceSettings(context, it).isCaptchaNotificationsDisabled
}.mapNotNull {
exceptionMap[it]
}
if (removedException != null) {
NotificationManagerCompat.from(context).cancel(TAG, removedException.source.hashCode())
}

View File

@@ -39,7 +39,7 @@ class ChapterPagesMenuProvider(
setOnActionExpandListener(this@ChapterPagesMenuProvider)
(actionView as? SearchView)?.setupChaptersSearchView()
}
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
menu.findItem(R.id.action_search)?.isVisible = viewModel.emptyReason.value == null
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
menu.findItem(R.id.action_downloaded)?.let { menuItem ->

View File

@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.plus
@@ -43,6 +44,7 @@ import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
@@ -97,9 +99,19 @@ abstract class ChaptersPagesViewModel(
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
val isChaptersEmpty: StateFlow<Boolean> = mangaDetails.map {
it != null && it.isLoaded && it.allChapters.isEmpty()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
val emptyReason: StateFlow<EmptyMangaReason?> = combine(
mangaDetails,
isLoading,
onError.onStart { emit(null) },
) { details, loading, error ->
when {
details == null || loading -> null
details.chapters.isNotEmpty() -> null
details.toManga().state == MangaState.RESTRICTED -> EmptyMangaReason.RESTRICTED
error != null -> EmptyMangaReason.LOADING_ERROR
else -> EmptyMangaReason.NO_CHAPTERS
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(), null)
val bookmarks = mangaDetails.flatMapLatest {
if (it != null) {

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.details.ui.pager
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
enum class EmptyMangaReason(
@StringRes val msgResId: Int,
) {
NO_CHAPTERS(R.string.no_chapters_in_manga),
LOADING_ERROR(R.string.chapters_load_failed),
RESTRICTED(R.string.manga_restricted_description),
}

View File

@@ -30,6 +30,7 @@ import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
@@ -96,8 +97,8 @@ class ChaptersFragment :
.flowOn(Dispatchers.Default)
.observe(viewLifecycleOwner, this::onChaptersChanged)
viewModel.quickFilter.observe(viewLifecycleOwner, this::onFilterChanged)
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
binding.textViewHolder.isVisible = it
viewModel.emptyReason.observe(viewLifecycleOwner) {
binding.textViewHolder.setTextAndVisible(it?.msgResId ?: 0)
}
}

View File

@@ -11,7 +11,6 @@ import androidx.appcompat.view.ActionMode
import androidx.collection.ArraySet
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -37,9 +36,11 @@ import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.databinding.FragmentPagesBinding
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
@@ -125,11 +126,18 @@ class PagesFragment :
it.spanCount = checkNotNull(spanResolver).spanCount
}
}
parentViewModel.isChaptersEmpty.observe(viewLifecycleOwner, ::onNoChaptersChanged)
parentViewModel.emptyReason.observe(viewLifecycleOwner, ::onNoChaptersChanged)
viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)
viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(binding.recyclerView))
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) }
combine(
viewModel.isLoading,
viewModel.thumbnails,
) { loading, content ->
loading && content.isEmpty()
}.observe(viewLifecycleOwner) {
binding.progressBar.showOrHide(it)
}
viewModel.isLoadingUp.observe(viewLifecycleOwner) { binding.progressBarTop.showOrHide(it) }
viewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) }
}
@@ -237,10 +245,10 @@ class PagesFragment :
spanResolver?.setGridSize(scale, requireViewBinding().recyclerView)
}
private fun onNoChaptersChanged(isNoChapters: Boolean) {
private fun onNoChaptersChanged(reason: EmptyMangaReason?) {
with(viewBinding ?: return) {
textViewHolder.isVisible = isNoChapters
recyclerView.isInvisible = isNoChapters
textViewHolder.setTextAndVisible(reason?.msgResId ?: 0)
recyclerView.isInvisible = reason != null
}
}

View File

@@ -5,8 +5,9 @@ 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.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
@@ -47,16 +48,15 @@ class PagesViewModel @Inject constructor(
)
init {
loadingJob = launchLoadingJob(Dispatchers.Default) {
val firstState = state.firstNotNull()
doInit(firstState)
launchJob(Dispatchers.Default) {
state.collectLatest {
if (it != null) {
launchJob(Dispatchers.Default) {
state.filterNotNull()
.collect {
val prevJob = loadingJob
loadingJob = launchLoadingJob(Dispatchers.Default) {
prevJob?.cancelAndJoin()
doInit(it)
}
}
}
}
}

View File

@@ -875,4 +875,7 @@
<string name="invalid_token">Invalid token: %s</string>
<string name="show_floating_control_button">Show floating control button</string>
<string name="unavailable">Unavailable</string>
<string name="manga_restricted_description">This manga is not available to read in this source. Try searching for it in other sources or opening it in a browser for more information</string>
<string name="no_chapters_in_manga">This manga does not contain any chapters</string>
<string name="chapters_load_failed">Failed to load chapter list</string>
</resources>