Surface image loading error in Reader (#1981)

This commit is contained in:
AwkwardPeak7
2025-04-13 19:46:12 +05:00
committed by GitHub
parent f1e2efcb37
commit fefa8f8498
9 changed files with 48 additions and 30 deletions

View File

@ -22,6 +22,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946)) - Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946))
- Deduplicate entries when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1957](https://github.com/mihonapp/mihon/pull/1957)) - Deduplicate entries when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1957](https://github.com/mihonapp/mihon/pull/1957))
- Update non-library manga data when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1967](https://github.com/mihonapp/mihon/pull/1967)) - Update non-library manga data when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1967](https://github.com/mihonapp/mihon/pull/1967))
- Surface image loading error in Reader ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#1981](https://github.com/mihonapp/mihon/pull/1981))
### Changed ### Changed
- Display all similarly named duplicates in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns), [@AntsyLich](https://github.com/AntsyLich)) ([#1861](https://github.com/mihonapp/mihon/pull/1861)) - Display all similarly named duplicates in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns), [@AntsyLich](https://github.com/AntsyLich)) ([#1861](https://github.com/mihonapp/mihon/pull/1861))

View File

@ -365,7 +365,7 @@ class Downloader(
try { try {
page.imageUrl = download.source.getImageUrl(page) page.imageUrl = download.source.getImageUrl(page)
} catch (e: Throwable) { } catch (e: Throwable) {
page.status = Page.State.Error page.status = Page.State.Error(e)
} }
} }
@ -457,7 +457,7 @@ class Downloader(
if (e is CancellationException) throw e if (e is CancellationException) throw e
// Mark this page as error and allow to download the remaining // Mark this page as error and allow to download the remaining
page.progress = 0 page.progress = 0
page.status = Page.State.Error page.status = Page.State.Error(e)
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id) notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
} }
} }

View File

@ -537,7 +537,7 @@ class ReaderViewModel @JvmOverloads constructor(
readerChapter.requestedPage = pageIndex readerChapter.requestedPage = pageIndex
chapterPageIndex = pageIndex chapterPageIndex = pageIndex
if (!incognitoMode && page.status != Page.State.Error) { if (!incognitoMode && page.status is Page.State.Error) {
readerChapter.chapter.last_page_read = pageIndex readerChapter.chapter.last_page_read = pageIndex
if (readerChapter.pages?.lastIndex == pageIndex) { if (readerChapter.pages?.lastIndex == pageIndex) {

View File

@ -88,7 +88,7 @@ internal class HttpPageLoader(
} }
// Automatically retry failed pages when subscribed to this page // Automatically retry failed pages when subscribed to this page
if (page.status == Page.State.Error) { if (page.status is Page.State.Error) {
page.status = Page.State.Queue page.status = Page.State.Queue
} }
@ -113,7 +113,7 @@ internal class HttpPageLoader(
* Retries a page. This method is only called from user interaction on the viewer. * Retries a page. This method is only called from user interaction on the viewer.
*/ */
override fun retryPage(page: ReaderPage) { override fun retryPage(page: ReaderPage) {
if (page.status == Page.State.Error) { if (page.status is Page.State.Error) {
page.status = Page.State.Queue page.status = Page.State.Queue
} }
queue.offer(PriorityPage(page, 2)) queue.offer(PriorityPage(page, 2))
@ -184,7 +184,7 @@ internal class HttpPageLoader(
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() } page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
page.status = Page.State.Ready page.status = Page.State.Ready
} catch (e: Throwable) { } catch (e: Throwable) {
page.status = Page.State.Error page.status = Page.State.Error(e)
if (e is CancellationException) { if (e is CancellationException) {
throw e throw e
} }

View File

@ -69,7 +69,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
private var config: Config? = null private var config: Config? = null
var onImageLoaded: (() -> Unit)? = null var onImageLoaded: (() -> Unit)? = null
var onImageLoadError: (() -> Unit)? = null var onImageLoadError: ((Throwable?) -> Unit)? = null
var onScaleChanged: ((newScale: Float) -> Unit)? = null var onScaleChanged: ((newScale: Float) -> Unit)? = null
var onViewClicked: (() -> Unit)? = null var onViewClicked: (() -> Unit)? = null
@ -85,8 +85,8 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
@CallSuper @CallSuper
open fun onImageLoadError() { open fun onImageLoadError(error: Throwable?) {
onImageLoadError?.invoke() onImageLoadError?.invoke(error)
} }
@CallSuper @CallSuper
@ -114,7 +114,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
override fun onImageLoadError(e: Exception) { override fun onImageLoadError(e: Exception) {
onImageLoadError() onImageLoadError(e)
} }
}, },
) )
@ -290,7 +290,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
override fun onImageLoadError(e: Exception) { override fun onImageLoadError(e: Exception) {
this@ReaderPageImageView.onImageLoadError() this@ReaderPageImageView.onImageLoadError(e)
} }
}, },
) )
@ -318,8 +318,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
setImage(ImageSource.bitmap(image.bitmap)) setImage(ImageSource.bitmap(image.bitmap))
isVisible = true isVisible = true
}, },
onError = { )
onImageLoadError() .listener(
onError = { _, result ->
onImageLoadError(result.throwable)
}, },
) )
.size(ViewSizeResolver(this@ReaderPageImageView)) .size(ViewSizeResolver(this@ReaderPageImageView))
@ -395,8 +397,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
isVisible = true isVisible = true
this@ReaderPageImageView.onImageLoaded() this@ReaderPageImageView.onImageLoaded()
}, },
onError = { )
this@ReaderPageImageView.onImageLoadError() .listener(
onError = { _, result ->
onImageLoadError(result.throwable)
}, },
) )
.crossfade(false) .crossfade(false)

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.view.isVisible import androidx.core.view.isVisible
import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.databinding.ReaderErrorBinding import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.InsertPage
@ -20,11 +21,13 @@ import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import okio.Buffer import okio.Buffer
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
/** /**
* View of the ViewPager that contains a page of a chapter. * View of the ViewPager that contains a page of a chapter.
@ -105,7 +108,7 @@ class PagerPageHolder(
} }
} }
Page.State.Ready -> setImage() Page.State.Ready -> setImage()
Page.State.Error -> setError() is Page.State.Error -> setError(state.error)
} }
} }
} }
@ -177,7 +180,7 @@ class PagerPageHolder(
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
withUIContext { withUIContext {
setError() setError(e)
} }
} }
} }
@ -242,9 +245,9 @@ class PagerPageHolder(
/** /**
* Called when the page has an error. * Called when the page has an error.
*/ */
private fun setError() { private fun setError(error: Throwable?) {
progressIndicator?.hide() progressIndicator?.hide()
showErrorLayout() showErrorLayout(error)
} }
override fun onImageLoaded() { override fun onImageLoaded() {
@ -255,9 +258,9 @@ class PagerPageHolder(
/** /**
* Called when an image fails to decode. * Called when an image fails to decode.
*/ */
override fun onImageLoadError() { override fun onImageLoadError(error: Throwable?) {
super.onImageLoadError() super.onImageLoadError(error)
setError() setError(error)
} }
/** /**
@ -268,7 +271,7 @@ class PagerPageHolder(
viewer.activity.hideMenu() viewer.activity.hideMenu()
} }
private fun showErrorLayout(): ReaderErrorBinding { private fun showErrorLayout(error: Throwable?): ReaderErrorBinding {
if (errorLayout == null) { if (errorLayout == null) {
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), this, true) errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), this, true)
errorLayout?.actionRetry?.viewer = viewer errorLayout?.actionRetry?.viewer = viewer
@ -289,6 +292,9 @@ class PagerPageHolder(
} }
} }
errorLayout?.errorMessage?.text = with(context) { error?.formattedMessage }
?: context.stringResource(MR.strings.decode_image_error)
errorLayout?.root?.isVisible = true errorLayout?.root?.isVisible = true
return errorLayout!! return errorLayout!!
} }

View File

@ -10,6 +10,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins import androidx.core.view.updateMargins
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.databinding.ReaderErrorBinding import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@ -25,11 +26,13 @@ import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import okio.Buffer import okio.Buffer
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
/** /**
* Holder of the webtoon reader for a single page of a chapter. * Holder of the webtoon reader for a single page of a chapter.
@ -81,7 +84,7 @@ class WebtoonPageHolder(
refreshLayoutParams() refreshLayoutParams()
frame.onImageLoaded = { onImageDecoded() } frame.onImageLoaded = { onImageDecoded() }
frame.onImageLoadError = { setError() } frame.onImageLoadError = { error -> setError(error) }
frame.onScaleChanged = { viewer.activity.hideMenu() } frame.onScaleChanged = { viewer.activity.hideMenu() }
} }
@ -145,7 +148,7 @@ class WebtoonPageHolder(
} }
} }
Page.State.Ready -> setImage() Page.State.Ready -> setImage()
Page.State.Error -> setError() is Page.State.Error -> setError(state.error)
} }
} }
} }
@ -207,7 +210,7 @@ class WebtoonPageHolder(
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
withUIContext { withUIContext {
setError() setError(e)
} }
} }
} }
@ -241,9 +244,9 @@ class WebtoonPageHolder(
/** /**
* Called when the page has an error. * Called when the page has an error.
*/ */
private fun setError() { private fun setError(error: Throwable?) {
progressContainer.isVisible = false progressContainer.isVisible = false
initErrorLayout() initErrorLayout(error)
} }
/** /**
@ -273,7 +276,7 @@ class WebtoonPageHolder(
/** /**
* Initializes a button to retry pages. * Initializes a button to retry pages.
*/ */
private fun initErrorLayout(): ReaderErrorBinding { private fun initErrorLayout(error: Throwable?): ReaderErrorBinding {
if (errorLayout == null) { if (errorLayout == null) {
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), frame, true) errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), frame, true)
errorLayout?.root?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, (parentHeight * 0.8).toInt()) errorLayout?.root?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, (parentHeight * 0.8).toInt())
@ -293,6 +296,9 @@ class WebtoonPageHolder(
} }
} }
errorLayout?.errorMessage?.text = with(context) { error?.formattedMessage }
?: context.stringResource(MR.strings.decode_image_error)
return errorLayout!! return errorLayout!!
} }

View File

@ -7,6 +7,7 @@
android:gravity="center"> android:gravity="center">
<TextView <TextView
android:id="@+id/error_message"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"

View File

@ -53,6 +53,6 @@ open class Page(
data object LoadPage : State data object LoadPage : State
data object DownloadImage : State data object DownloadImage : State
data object Ready : State data object Ready : State
data object Error : State data class Error(val error: Throwable) : State
} }
} }