Fix unsupported image formats in reader
parent
0726c037a4
commit
b103589bba
@ -0,0 +1,106 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.reader.base
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||||
|
import org.koitharu.kotatsu.ui.reader.PageLoader
|
||||||
|
import org.koitharu.kotatsu.utils.ext.launchAfter
|
||||||
|
import org.koitharu.kotatsu.utils.ext.launchInstead
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class PageHolderDelegate(
|
||||||
|
private val loader: PageLoader,
|
||||||
|
private val callback: Callback
|
||||||
|
) : SubsamplingScaleImageView.DefaultOnImageEventListener(), CoroutineScope by loader {
|
||||||
|
|
||||||
|
private var state = State.EMPTY
|
||||||
|
private var job: Job? = null
|
||||||
|
private var file: File? = null
|
||||||
|
|
||||||
|
fun onBind(page: MangaPage) {
|
||||||
|
doLoad(page, force = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retry(page: MangaPage) {
|
||||||
|
doLoad(page, force = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRecycle() {
|
||||||
|
state = State.EMPTY
|
||||||
|
file = null
|
||||||
|
job?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReady() {
|
||||||
|
state = State.SHOWING
|
||||||
|
callback.onImageShowing()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoaded() {
|
||||||
|
state = State.SHOWN
|
||||||
|
callback.onImageShown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoadError(e: Exception) {
|
||||||
|
val file = this.file
|
||||||
|
if (state == State.LOADED && e is IOException && file != null && file.exists()) {
|
||||||
|
job = launchAfter(job) {
|
||||||
|
state = State.CONVERTING
|
||||||
|
try {
|
||||||
|
loader.convertInPlace(file)
|
||||||
|
state = State.CONVERTED
|
||||||
|
callback.onImageReady(file.toUri())
|
||||||
|
} catch (e2: Throwable) {
|
||||||
|
e2.addSuppressed(e)
|
||||||
|
state = State.ERROR
|
||||||
|
callback.onError(e2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state = State.ERROR
|
||||||
|
callback.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doLoad(data: MangaPage, force: Boolean) {
|
||||||
|
job = launchInstead(job) {
|
||||||
|
state = State.LOADING
|
||||||
|
callback.onLoadingStarted()
|
||||||
|
try {
|
||||||
|
val file = withContext(Dispatchers.IO) {
|
||||||
|
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
|
||||||
|
loader.loadFile(pageUrl, force)
|
||||||
|
}
|
||||||
|
this@PageHolderDelegate.file = file
|
||||||
|
state = State.LOADED
|
||||||
|
callback.onImageReady(file.toUri())
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
//do nothing
|
||||||
|
} catch (e: Exception) {
|
||||||
|
state = State.ERROR
|
||||||
|
callback.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class State {
|
||||||
|
EMPTY, LOADING, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
|
||||||
|
fun onLoadingStarted()
|
||||||
|
|
||||||
|
fun onError(e: Throwable)
|
||||||
|
|
||||||
|
fun onImageReady(uri: Uri)
|
||||||
|
|
||||||
|
fun onImageShowing()
|
||||||
|
|
||||||
|
fun onImageShown()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,100 +1,87 @@
|
|||||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import kotlinx.android.synthetic.main.item_page_webtoon.*
|
import kotlinx.android.synthetic.main.item_page_webtoon.*
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
|
||||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||||
import org.koitharu.kotatsu.ui.reader.PageLoader
|
import org.koitharu.kotatsu.ui.reader.PageLoader
|
||||||
|
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
|
||||||
|
|
||||||
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||||
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon),
|
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon),
|
||||||
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
|
PageHolderDelegate.Callback, View.OnClickListener {
|
||||||
|
|
||||||
private var job: Job? = null
|
private val delegate = PageHolderDelegate(loader, this)
|
||||||
private var scrollToRestore = 0
|
private var scrollToRestore = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
ssiv.setOnImageEventListener(this)
|
ssiv.setOnImageEventListener(delegate)
|
||||||
button_retry.setOnClickListener {
|
button_retry.setOnClickListener(this)
|
||||||
doLoad(boundData ?: return@setOnClickListener, force = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(data: MangaPage, extra: Unit) {
|
override fun onBind(data: MangaPage, extra: Unit) {
|
||||||
doLoad(data, force = false)
|
delegate.onBind(data)
|
||||||
}
|
|
||||||
|
|
||||||
private fun doLoad(data: MangaPage, force: Boolean) {
|
|
||||||
job?.cancel()
|
|
||||||
scrollToRestore = 0
|
|
||||||
job = launch {
|
|
||||||
layout_error.isVisible = false
|
|
||||||
progressBar.isVisible = true
|
|
||||||
ssiv.recycle()
|
|
||||||
try {
|
|
||||||
val uri = withContext(Dispatchers.IO) {
|
|
||||||
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
|
|
||||||
loader.loadFile(pageUrl, force)
|
|
||||||
}.toUri()
|
|
||||||
ssiv.setImage(ImageSource.uri(uri))
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
//do nothing
|
|
||||||
} catch (e: Exception) {
|
|
||||||
onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRecycled() {
|
override fun onRecycled() {
|
||||||
job?.cancel()
|
delegate.onRecycle()
|
||||||
ssiv.recycle()
|
ssiv.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getScrollY() = ssiv.getScroll()
|
override fun onLoadingStarted() {
|
||||||
|
layout_error.isVisible = false
|
||||||
|
progressBar.isVisible = true
|
||||||
|
ssiv.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
fun restoreScroll(scroll: Int) {
|
override fun onImageReady(uri: Uri) {
|
||||||
if (ssiv.isReady) {
|
ssiv.setImage(ImageSource.uri(uri))
|
||||||
ssiv.scrollTo(scroll)
|
|
||||||
} else {
|
|
||||||
scrollToRestore = scroll
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReady() {
|
override fun onImageShowing() {
|
||||||
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
|
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
|
||||||
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||||
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
||||||
ssiv.scrollTo(when {
|
ssiv.scrollTo(
|
||||||
scrollToRestore != 0 -> scrollToRestore
|
when {
|
||||||
itemView.top < 0 -> ssiv.getScrollRange()
|
scrollToRestore != 0 -> scrollToRestore
|
||||||
else -> 0
|
itemView.top < 0 -> ssiv.getScrollRange()
|
||||||
})
|
else -> 0
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onImageLoadError(e: Exception) = onError(e)
|
override fun onImageShown() {
|
||||||
|
|
||||||
override fun onImageLoaded() {
|
|
||||||
progressBar.isVisible = false
|
progressBar.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTileLoadError(e: Exception?) = Unit
|
override fun onClick(v: View) {
|
||||||
|
when (v.id) {
|
||||||
override fun onPreviewReleased() = Unit
|
R.id.button_retry -> delegate.retry(boundData ?: return)
|
||||||
|
}
|
||||||
override fun onPreviewLoadError(e: Exception?) = Unit
|
}
|
||||||
|
|
||||||
private fun onError(e: Throwable) {
|
override fun onError(e: Throwable) {
|
||||||
textView_error.text = e.getDisplayMessage(context.resources)
|
textView_error.text = e.getDisplayMessage(context.resources)
|
||||||
layout_error.isVisible = true
|
layout_error.isVisible = true
|
||||||
progressBar.isVisible = false
|
progressBar.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScrollY() = ssiv.getScroll()
|
||||||
|
|
||||||
|
fun restoreScroll(scroll: Int) {
|
||||||
|
if (ssiv.isReady) {
|
||||||
|
ssiv.scrollTo(scroll)
|
||||||
|
} else {
|
||||||
|
scrollToRestore = scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue