Unify reader error layout (#6512)

So nobody will think that the error layout is broken when they see different
layout.
This commit is contained in:
Ivan Iskandar 2022-02-03 09:41:20 +07:00 committed by GitHub
parent b6553bdc34
commit 7108993936
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 214 deletions

View file

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.ui.reader.viewer
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import com.google.android.material.button.MaterialButton
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
/**
* A button class to be used by child views of the pager viewer. All tap gestures are handled by
* the pager, but this class disables that behavior to allow clickable buttons.
*/
class ReaderButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialButtonStyle
) : MaterialButton(context, attrs, defStyleAttr) {
var viewer: PagerViewer? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
viewer?.pager?.setGestureDetectorEnabled(false)
if (event.actionMasked == MotionEvent.ACTION_UP) {
viewer?.pager?.setGestureDetectorEnabled(true)
}
return super.onTouchEvent(event)
}
}

View file

@ -1,24 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.viewer.pager
import android.annotation.SuppressLint
import android.content.Context
import android.view.MotionEvent
import com.google.android.material.button.MaterialButton
/**
* A button class to be used by child views of the pager viewer. All tap gestures are handled by
* the pager, but this class disables that behavior to allow clickable buttons.
*/
@SuppressLint("ViewConstructor")
class PagerButton(context: Context, viewer: PagerViewer) : MaterialButton(context) {
init {
setOnTouchListener { _, event ->
viewer.pager.setGestureDetectorEnabled(false)
if (event.actionMasked == MotionEvent.ACTION_UP) {
viewer.pager.setGestureDetectorEnabled(true)
}
false
}
}
}

View file

@ -3,14 +3,10 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.Gravity import android.view.Gravity
import android.view.ViewGroup import android.view.LayoutInflater
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.setMargins
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import com.google.android.material.textview.MaterialTextView import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
import eu.kanade.tachiyomi.R
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
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@ -18,7 +14,6 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
@ -54,14 +49,9 @@ class PagerPageHolder(
} }
/** /**
* Retry button used to allow retrying. * Error layout to show when the image fails to load.
*/ */
private var retryButton: PagerButton? = null private var errorLayout: ReaderErrorBinding? = null
/**
* Error layout to show when the image fails to decode.
*/
private var decodeErrorLayout: ViewGroup? = null
/** /**
* Subscription for status changes of the page. * Subscription for status changes of the page.
@ -176,8 +166,7 @@ class PagerPageHolder(
*/ */
private fun setQueued() { private fun setQueued() {
progressIndicator.show() progressIndicator.show()
retryButton?.isVisible = false errorLayout?.root?.isVisible = false
decodeErrorLayout?.isVisible = false
} }
/** /**
@ -185,8 +174,7 @@ class PagerPageHolder(
*/ */
private fun setLoading() { private fun setLoading() {
progressIndicator.show() progressIndicator.show()
retryButton?.isVisible = false errorLayout?.root?.isVisible = false
decodeErrorLayout?.isVisible = false
} }
/** /**
@ -194,8 +182,7 @@ class PagerPageHolder(
*/ */
private fun setDownloading() { private fun setDownloading() {
progressIndicator.show() progressIndicator.show()
retryButton?.isVisible = false errorLayout?.root?.isVisible = false
decodeErrorLayout?.isVisible = false
} }
/** /**
@ -203,8 +190,7 @@ class PagerPageHolder(
*/ */
private fun setImage() { private fun setImage() {
progressIndicator.setProgress(0) progressIndicator.setProgress(0)
retryButton?.isVisible = false errorLayout?.root?.isVisible = false
decodeErrorLayout?.isVisible = false
unsubscribeReadImageHeader() unsubscribeReadImageHeader()
val streamFn = page.stream ?: return val streamFn = page.stream ?: return
@ -299,7 +285,7 @@ class PagerPageHolder(
*/ */
private fun setError() { private fun setError() {
progressIndicator.hide() progressIndicator.hide()
initRetryButton().isVisible = true showErrorLayout(withOpenInWebView = false)
} }
override fun onImageLoaded() { override fun onImageLoaded() {
@ -313,7 +299,7 @@ class PagerPageHolder(
override fun onImageLoadError() { override fun onImageLoadError() {
super.onImageLoadError() super.onImageLoadError()
progressIndicator.hide() progressIndicator.hide()
initDecodeErrorLayout().isVisible = true showErrorLayout(withOpenInWebView = true)
} }
/** /**
@ -324,78 +310,24 @@ class PagerPageHolder(
viewer.activity.hideMenu() viewer.activity.hideMenu()
} }
/** private fun showErrorLayout(withOpenInWebView: Boolean): ReaderErrorBinding {
* Initializes a button to retry pages. if (errorLayout == null) {
*/ errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), this, true)
private fun initRetryButton(): PagerButton { errorLayout?.actionRetry?.viewer = viewer
if (retryButton != null) return retryButton!! errorLayout?.actionRetry?.setOnClickListener {
retryButton = PagerButton(context, viewer).apply {
layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
setText(R.string.action_retry)
setOnClickListener {
page.chapter.pageLoader?.retryPage(page) page.chapter.pageLoader?.retryPage(page)
} }
}
addView(retryButton)
return retryButton!!
}
/**
* Initializes a decode error layout.
*/
private fun initDecodeErrorLayout(): ViewGroup {
if (decodeErrorLayout != null) return decodeErrorLayout!!
val margins = 8.dpToPx
val decodeLayout = LinearLayout(context).apply {
gravity = Gravity.CENTER
orientation = LinearLayout.VERTICAL
}
decodeErrorLayout = decodeLayout
MaterialTextView(context).apply {
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(margins)
}
gravity = Gravity.CENTER
setText(R.string.decode_image_error)
decodeLayout.addView(this)
}
PagerButton(context, viewer).apply {
layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(margins)
}
setText(R.string.action_retry)
setOnClickListener {
page.chapter.pageLoader?.retryPage(page)
}
decodeLayout.addView(this)
}
val imageUrl = page.imageUrl val imageUrl = page.imageUrl
if (imageUrl.orEmpty().startsWith("http", true)) { if (imageUrl.orEmpty().startsWith("http", true)) {
PagerButton(context, viewer).apply { errorLayout?.actionOpenInWebView?.viewer = viewer
layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { errorLayout?.actionOpenInWebView?.setOnClickListener {
setMargins(margins)
}
setText(R.string.action_open_in_web_view)
setOnClickListener {
val intent = WebViewActivity.newIntent(context, imageUrl!!) val intent = WebViewActivity.newIntent(context, imageUrl!!)
context.startActivity(intent) context.startActivity(intent)
} }
decodeLayout.addView(this)
} }
} }
errorLayout?.actionOpenInWebView?.isVisible = withOpenInWebView
addView(decodeLayout) errorLayout?.root?.isVisible = true
return decodeLayout return errorLayout!!
} }
} }

View file

@ -13,6 +13,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.ViewPagerAdapter
@ -126,13 +127,14 @@ class PagerTransitionHolder(
text = context.getString(R.string.transition_pages_error, error.message) text = context.getString(R.string.transition_pages_error, error.message)
} }
val retryBtn = PagerButton(context, viewer).apply { val retryBtn = ReaderButton(context).apply {
viewer = this@PagerTransitionHolder.viewer
wrapContent() wrapContent()
setText(R.string.action_retry) setText(R.string.action_retry)
setOnClickListener { setOnClickListener {
val toChapter = transition.to val toChapter = transition.to
if (toChapter != null) { if (toChapter != null) {
viewer.activity.requestPreloadChapter(toChapter) this@PagerTransitionHolder.viewer.activity.requestPreloadChapter(toChapter)
} }
} }
} }

View file

@ -2,18 +2,16 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
import android.content.res.Resources import android.content.res.Resources
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.core.view.isVisible 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 com.google.android.material.button.MaterialButton import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
import com.google.android.material.textview.MaterialTextView
import eu.kanade.tachiyomi.R
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
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
@ -52,14 +50,9 @@ class WebtoonPageHolder(
private lateinit var progressContainer: ViewGroup private lateinit var progressContainer: ViewGroup
/** /**
* Retry button container used to allow retrying. * Error layout to show when the image fails to load.
*/ */
private var retryContainer: ViewGroup? = null private var errorLayout: ReaderErrorBinding? = null
/**
* Error layout to show when the image fails to decode.
*/
private var decodeErrorLayout: ViewGroup? = null
/** /**
* Getter to retrieve the height of the recycler view. * Getter to retrieve the height of the recycler view.
@ -125,7 +118,7 @@ class WebtoonPageHolder(
unsubscribeProgress() unsubscribeProgress()
unsubscribeReadImageHeader() unsubscribeReadImageHeader()
removeDecodeErrorLayout() removeErrorLayout()
frame.recycle() frame.recycle()
progressIndicator.setProgress(0, animated = false) progressIndicator.setProgress(0, animated = false)
} }
@ -219,8 +212,7 @@ class WebtoonPageHolder(
private fun setQueued() { private fun setQueued() {
progressContainer.isVisible = true progressContainer.isVisible = true
progressIndicator.show() progressIndicator.show()
retryContainer?.isVisible = false removeErrorLayout()
removeDecodeErrorLayout()
} }
/** /**
@ -229,8 +221,7 @@ class WebtoonPageHolder(
private fun setLoading() { private fun setLoading() {
progressContainer.isVisible = true progressContainer.isVisible = true
progressIndicator.show() progressIndicator.show()
retryContainer?.isVisible = false removeErrorLayout()
removeDecodeErrorLayout()
} }
/** /**
@ -239,8 +230,7 @@ class WebtoonPageHolder(
private fun setDownloading() { private fun setDownloading() {
progressContainer.isVisible = true progressContainer.isVisible = true
progressIndicator.show() progressIndicator.show()
retryContainer?.isVisible = false removeErrorLayout()
removeDecodeErrorLayout()
} }
/** /**
@ -248,8 +238,7 @@ class WebtoonPageHolder(
*/ */
private fun setImage() { private fun setImage() {
progressIndicator.setProgress(0) progressIndicator.setProgress(0)
retryContainer?.isVisible = false removeErrorLayout()
removeDecodeErrorLayout()
unsubscribeReadImageHeader() unsubscribeReadImageHeader()
val streamFn = page?.stream ?: return val streamFn = page?.stream ?: return
@ -302,7 +291,7 @@ class WebtoonPageHolder(
*/ */
private fun setError() { private fun setError() {
progressContainer.isVisible = false progressContainer.isVisible = false
initRetryLayout().isVisible = true initErrorLayout(withOpenInWebView = false)
} }
/** /**
@ -317,7 +306,7 @@ class WebtoonPageHolder(
*/ */
private fun onImageDecodeError() { private fun onImageDecodeError() {
progressContainer.isVisible = false progressContainer.isVisible = false
initDecodeErrorLayout().isVisible = true initErrorLayout(withOpenInWebView = true)
} }
/** /**
@ -340,94 +329,32 @@ class WebtoonPageHolder(
/** /**
* Initializes a button to retry pages. * Initializes a button to retry pages.
*/ */
private fun initRetryLayout(): ViewGroup { private fun initErrorLayout(withOpenInWebView: Boolean): ReaderErrorBinding {
if (retryContainer != null) return retryContainer!! if (errorLayout == null) {
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), frame, true)
retryContainer = FrameLayout(context) errorLayout?.root?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, (parentHeight * 0.8).toInt())
frame.addView(retryContainer, MATCH_PARENT, parentHeight) errorLayout?.actionRetry?.setOnClickListener {
MaterialButton(context).apply {
layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
gravity = Gravity.CENTER_HORIZONTAL
setMargins(0, parentHeight / 4, 0, 0)
}
setText(R.string.action_retry)
setOnClickListener {
page?.let { it.chapter.pageLoader?.retryPage(it) } page?.let { it.chapter.pageLoader?.retryPage(it) }
} }
retryContainer!!.addView(this)
}
return retryContainer!!
}
/**
* Initializes a decode error layout.
*/
private fun initDecodeErrorLayout(): ViewGroup {
if (decodeErrorLayout != null) return decodeErrorLayout!!
val margins = 8.dpToPx
val decodeLayout = LinearLayout(context).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply {
setMargins(0, parentHeight / 6, 0, 0)
}
gravity = Gravity.CENTER_HORIZONTAL
orientation = LinearLayout.VERTICAL
}
decodeErrorLayout = decodeLayout
MaterialTextView(context).apply {
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, margins, 0, margins)
}
gravity = Gravity.CENTER
setText(R.string.decode_image_error)
decodeLayout.addView(this)
}
MaterialButton(context).apply {
layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, margins, 0, margins)
}
setText(R.string.action_retry)
setOnClickListener {
page?.let { it.chapter.pageLoader?.retryPage(it) }
}
decodeLayout.addView(this)
}
val imageUrl = page?.imageUrl val imageUrl = page?.imageUrl
if (imageUrl.orEmpty().startsWith("http", true)) { if (imageUrl.orEmpty().startsWith("http", true)) {
MaterialButton(context).apply { errorLayout?.actionOpenInWebView?.setOnClickListener {
layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, margins, 0, margins)
}
setText(R.string.action_open_in_web_view)
setOnClickListener {
val intent = WebViewActivity.newIntent(context, imageUrl!!) val intent = WebViewActivity.newIntent(context, imageUrl!!)
context.startActivity(intent) context.startActivity(intent)
} }
decodeLayout.addView(this)
} }
} }
errorLayout?.actionOpenInWebView?.isVisible = withOpenInWebView
frame.addView(decodeLayout) return errorLayout!!
return decodeLayout
} }
/** /**
* Removes the decode error layout from the holder, if found. * Removes the decode error layout from the holder, if found.
*/ */
private fun removeDecodeErrorLayout() { private fun removeErrorLayout() {
val layout = decodeErrorLayout errorLayout?.let {
if (layout != null) { frame.removeView(it.root)
frame.removeView(layout) errorLayout = null
decodeErrorLayout = null
} }
} }
} }

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/decode_image_error"
android:textAppearance="?attr/textAppearanceBodyMedium" />
<eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton
android:id="@+id/action_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/action_retry" />
<eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton
android:id="@+id/action_open_in_web_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/action_open_in_web_view"
android:visibility="gone" />
</LinearLayout>