diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/CaptchaHandler.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/CaptchaHandler.kt index d43f295a0..081c853f5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/CaptchaHandler.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/CaptchaHandler.kt @@ -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()) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChapterPagesMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChapterPagesMenuProvider.kt index fb5227cc1..8fae8d2e7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChapterPagesMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChapterPagesMenuProvider.kt @@ -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 -> diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt index 2c0f7a467..793eab78c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt @@ -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 = mangaDetails.map { - it != null && it.isLoaded && it.allChapters.isEmpty() - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) + val emptyReason: StateFlow = 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) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/EmptyMangaReason.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/EmptyMangaReason.kt new file mode 100644 index 000000000..6ed9cdbb2 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/EmptyMangaReason.kt @@ -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), +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt index 56db72fd3..037d4388d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt @@ -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) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt index 17d498034..f73c44c35 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt @@ -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 } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt index 084602d2f..515da8123 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt @@ -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) } } - } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfc665306..aa0a42eca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -875,4 +875,7 @@ Invalid token: %s Show floating control button Unavailable + 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 + This manga does not contain any chapters + Failed to load chapter list