Show reason why manga has no chapters
This commit is contained in:
@@ -104,14 +104,14 @@ class CaptchaHandler @Inject constructor(
|
|||||||
val dao = databaseProvider.get().getSourcesDao()
|
val dao = databaseProvider.get().getSourcesDao()
|
||||||
dao.setCfState(source.name, exception?.state ?: CloudFlareHelper.PROTECTION_NOT_DETECTED)
|
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)) {
|
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) {
|
if (removedException != null) {
|
||||||
NotificationManagerCompat.from(context).cancel(TAG, removedException.source.hashCode())
|
NotificationManagerCompat.from(context).cancel(TAG, removedException.source.hashCode())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ChapterPagesMenuProvider(
|
|||||||
setOnActionExpandListener(this@ChapterPagesMenuProvider)
|
setOnActionExpandListener(this@ChapterPagesMenuProvider)
|
||||||
(actionView as? SearchView)?.setupChaptersSearchView()
|
(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_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
|
||||||
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
|
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
|
||||||
menu.findItem(R.id.action_downloaded)?.let { menuItem ->
|
menu.findItem(R.id.action_downloaded)?.let { menuItem ->
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.plus
|
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.DeleteLocalMangaUseCase
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
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.ReaderActivity
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||||
@@ -97,9 +99,19 @@ abstract class ChaptersPagesViewModel(
|
|||||||
}
|
}
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
|
||||||
|
|
||||||
val isChaptersEmpty: StateFlow<Boolean> = mangaDetails.map {
|
val emptyReason: StateFlow<EmptyMangaReason?> = combine(
|
||||||
it != null && it.isLoaded && it.allChapters.isEmpty()
|
mangaDetails,
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
|
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 {
|
val bookmarks = mangaDetails.flatMapLatest {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -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.findAppCompatDelegate
|
||||||
import org.koitharu.kotatsu.core.util.ext.findParentCallback
|
import org.koitharu.kotatsu.core.util.ext.findParentCallback
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
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.databinding.FragmentChaptersBinding
|
||||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
||||||
@@ -96,8 +97,8 @@ class ChaptersFragment :
|
|||||||
.flowOn(Dispatchers.Default)
|
.flowOn(Dispatchers.Default)
|
||||||
.observe(viewLifecycleOwner, this::onChaptersChanged)
|
.observe(viewLifecycleOwner, this::onChaptersChanged)
|
||||||
viewModel.quickFilter.observe(viewLifecycleOwner, this::onFilterChanged)
|
viewModel.quickFilter.observe(viewLifecycleOwner, this::onFilterChanged)
|
||||||
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
viewModel.emptyReason.observe(viewLifecycleOwner) {
|
||||||
binding.textViewHolder.isVisible = it
|
binding.textViewHolder.setTextAndVisible(it?.msgResId ?: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import androidx.appcompat.view.ActionMode
|
|||||||
import androidx.collection.ArraySet
|
import androidx.collection.ArraySet
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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.findParentCallback
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
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.core.util.ext.showOrHide
|
||||||
import org.koitharu.kotatsu.databinding.FragmentPagesBinding
|
import org.koitharu.kotatsu.databinding.FragmentPagesBinding
|
||||||
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
|
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.GridSpanResolver
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
||||||
@@ -125,11 +126,18 @@ class PagesFragment :
|
|||||||
it.spanCount = checkNotNull(spanResolver).spanCount
|
it.spanCount = checkNotNull(spanResolver).spanCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parentViewModel.isChaptersEmpty.observe(viewLifecycleOwner, ::onNoChaptersChanged)
|
parentViewModel.emptyReason.observe(viewLifecycleOwner, ::onNoChaptersChanged)
|
||||||
viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)
|
viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)
|
||||||
viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(binding.recyclerView))
|
viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(binding.recyclerView))
|
||||||
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
|
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.isLoadingUp.observe(viewLifecycleOwner) { binding.progressBarTop.showOrHide(it) }
|
||||||
viewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) }
|
viewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) }
|
||||||
}
|
}
|
||||||
@@ -237,10 +245,10 @@ class PagesFragment :
|
|||||||
spanResolver?.setGridSize(scale, requireViewBinding().recyclerView)
|
spanResolver?.setGridSize(scale, requireViewBinding().recyclerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onNoChaptersChanged(isNoChapters: Boolean) {
|
private fun onNoChaptersChanged(reason: EmptyMangaReason?) {
|
||||||
with(viewBinding ?: return) {
|
with(viewBinding ?: return) {
|
||||||
textViewHolder.isVisible = isNoChapters
|
textViewHolder.setTextAndVisible(reason?.msgResId ?: 0)
|
||||||
recyclerView.isInvisible = isNoChapters
|
recyclerView.isInvisible = reason != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ 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.collectLatest
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
@@ -47,16 +48,15 @@ class PagesViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val firstState = state.firstNotNull()
|
state.filterNotNull()
|
||||||
doInit(firstState)
|
.collect {
|
||||||
launchJob(Dispatchers.Default) {
|
val prevJob = loadingJob
|
||||||
state.collectLatest {
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
if (it != null) {
|
prevJob?.cancelAndJoin()
|
||||||
doInit(it)
|
doInit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -875,4 +875,7 @@
|
|||||||
<string name="invalid_token">Invalid token: %s</string>
|
<string name="invalid_token">Invalid token: %s</string>
|
||||||
<string name="show_floating_control_button">Show floating control button</string>
|
<string name="show_floating_control_button">Show floating control button</string>
|
||||||
<string name="unavailable">Unavailable</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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user