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
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import kotlinx.android.synthetic.main.item_page_webtoon.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.koitharu.kotatsu.R
|
||||
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.reader.PageLoader
|
||||
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
|
||||
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
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
|
||||
|
||||
init {
|
||||
ssiv.setOnImageEventListener(this)
|
||||
button_retry.setOnClickListener {
|
||||
doLoad(boundData ?: return@setOnClickListener, force = true)
|
||||
}
|
||||
ssiv.setOnImageEventListener(delegate)
|
||||
button_retry.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onBind(data: MangaPage, extra: Unit) {
|
||||
doLoad(data, force = false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
delegate.onBind(data)
|
||||
}
|
||||
|
||||
override fun onRecycled() {
|
||||
job?.cancel()
|
||||
delegate.onRecycle()
|
||||
ssiv.recycle()
|
||||
}
|
||||
|
||||
fun getScrollY() = ssiv.getScroll()
|
||||
override fun onLoadingStarted() {
|
||||
layout_error.isVisible = false
|
||||
progressBar.isVisible = true
|
||||
ssiv.recycle()
|
||||
}
|
||||
|
||||
fun restoreScroll(scroll: Int) {
|
||||
if (ssiv.isReady) {
|
||||
ssiv.scrollTo(scroll)
|
||||
} else {
|
||||
scrollToRestore = scroll
|
||||
}
|
||||
override fun onImageReady(uri: Uri) {
|
||||
ssiv.setImage(ImageSource.uri(uri))
|
||||
}
|
||||
|
||||
override fun onReady() {
|
||||
override fun onImageShowing() {
|
||||
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.scrollTo(when {
|
||||
scrollToRestore != 0 -> scrollToRestore
|
||||
itemView.top < 0 -> ssiv.getScrollRange()
|
||||
else -> 0
|
||||
})
|
||||
ssiv.scrollTo(
|
||||
when {
|
||||
scrollToRestore != 0 -> scrollToRestore
|
||||
itemView.top < 0 -> ssiv.getScrollRange()
|
||||
else -> 0
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onImageLoadError(e: Exception) = onError(e)
|
||||
|
||||
override fun onImageLoaded() {
|
||||
override fun onImageShown() {
|
||||
progressBar.isVisible = false
|
||||
}
|
||||
|
||||
override fun onTileLoadError(e: Exception?) = Unit
|
||||
|
||||
override fun onPreviewReleased() = Unit
|
||||
|
||||
override fun onPreviewLoadError(e: Exception?) = Unit
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_retry -> delegate.retry(boundData ?: return)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
override fun onError(e: Throwable) {
|
||||
textView_error.text = e.getDisplayMessage(context.resources)
|
||||
layout_error.isVisible = true
|
||||
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