Fix loading empty manga

(cherry picked from commit 5590ab7c8a)
This commit is contained in:
Koitharu
2025-11-02 15:12:32 +02:00
parent 8513bc6daf
commit f59027666b
6 changed files with 1528 additions and 1463 deletions

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.exceptions
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
import org.koitharu.kotatsu.parsers.model.Manga
class EmptyMangaException(
val reason: EmptyMangaReason?,
val manga: Manga,
cause: Throwable?
) : IllegalStateException(cause)

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyMangaException
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
@@ -25,6 +26,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.core.util.ext.isHttpUrl
import org.koitharu.kotatsu.core.util.ext.restartApplication
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.Manga
@@ -83,6 +85,16 @@ class ExceptionResolver private constructor(
false
}
is EmptyMangaException -> {
when (e.reason) {
EmptyMangaReason.NO_CHAPTERS -> openAlternatives(e.manga)
EmptyMangaReason.LOADING_ERROR -> Unit
EmptyMangaReason.RESTRICTED -> host.router.openBrowser(e.manga)
else -> Unit
}
false
}
is UnsupportedSourceException -> {
e.manga?.let { openAlternatives(it) }
false
@@ -229,6 +241,12 @@ class ExceptionResolver private constructor(
is InteractiveActionRequiredException -> R.string._continue
is EmptyMangaException -> when (e.reason) {
EmptyMangaReason.RESTRICTED -> if (e.manga.publicUrl.isHttpUrl()) R.string.open_in_browser else 0
EmptyMangaReason.NO_CHAPTERS -> R.string.alternatives
else -> 0
}
else -> 0
}

View File

@@ -215,6 +215,12 @@ class AppRouter private constructor(
startActivity(browserIntent(contextOrNull() ?: return, url, source, title))
}
fun openBrowser(manga: Manga) = openBrowser(
url = manga.publicUrl,
source = manga.source,
title = manga.title,
)
fun openColorFilterConfig(manga: Manga, page: MangaPage) {
startActivity(
Intent(contextOrNull(), ColorFilterConfigActivity::class.java)

View File

@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.EmptyMangaException
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
@@ -103,6 +104,7 @@ private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = w
is AccessDeniedException -> resources.getString(R.string.no_access_to_file)
is NonFileUriException -> resources.getString(R.string.error_non_file_uri)
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
is EmptyMangaException -> reason?.let { resources.getString(it.msgResId) } ?: cause?.getDisplayMessage(resources)
is ProxyConfigException -> resources.getString(R.string.invalid_proxy_configuration)
is SyncApiException,
is ContentUnavailableException -> message
@@ -167,6 +169,8 @@ fun Throwable.getCauseUrl(): String? = when (this) {
is CloudFlareProtectedException -> url
is InteractiveActionRequiredException -> url
is HttpStatusException -> url
is UnsupportedSourceException -> manga?.publicUrl?.takeIf { it.isHttpUrl() }
is EmptyMangaException -> manga.publicUrl.takeIf { it.isHttpUrl() }
is HttpException -> (response.delegate as? Response)?.request?.url?.toString()
else -> null
}

View File

@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.ui.model.MangaOverride
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.reader.data.filterChapters
@@ -50,6 +51,9 @@ data class MangaDetails(
.ifNullOrEmpty { localManga?.manga?.coverUrl }
?.nullIfEmpty()
val isRestricted: Boolean
get() = manga.state == MangaState.RESTRICTED
private val mergedManga by lazy {
if (localManga == null) {
// fast path

View File

@@ -29,6 +29,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.exceptions.EmptyMangaException
import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.nav.ReaderIntent
@@ -47,6 +48,7 @@ import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.domain.DetailsInteractor
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.domain.HistoryUpdateUseCase
@@ -405,9 +407,11 @@ class ReaderViewModel @Inject constructor(
private fun loadImpl() {
loadingJob = launchLoadingJob(Dispatchers.Default + EventExceptionHandler(onLoadingError)) {
var exception: Exception? = null
var loadedDetails: MangaDetails? = null
try {
detailsLoadUseCase(intent, force = false)
.collect { details ->
loadedDetails = details
if (mangaDetails.value == null) {
mangaDetails.value = details
}
@@ -452,9 +456,28 @@ class ReaderViewModel @Inject constructor(
exception = e.mergeWith(exception)
}
if (readingState.value == null) {
onLoadingError.call(
exception ?: IllegalStateException("Unable to load manga. This should never happen. Please report"),
val loadedManga = loadedDetails // for smart cast
if (loadedManga != null) {
mangaDetails.value = loadedManga.filterChapters(selectedBranch.value)
}
val loadingError = when {
exception != null -> exception
loadedManga == null || !loadedManga.isLoaded -> null
loadedManga.isRestricted -> EmptyMangaException(
EmptyMangaReason.RESTRICTED,
loadedManga.toManga(),
null,
)
loadedManga.allChapters.isEmpty() -> EmptyMangaException(
EmptyMangaReason.NO_CHAPTERS,
loadedManga.toManga(),
null,
)
else -> null
} ?: IllegalStateException("Unable to load manga. This should never happen. Please report")
onLoadingError.call(loadingError)
} else exception?.let { e ->
// manga has been loaded but error occurred
errorEvent.call(e)