Retry thumbnails loading after network state changed
This commit is contained in:
@@ -7,6 +7,7 @@ import androidx.annotation.AttrRes
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.core.content.withStyledAttributes
|
import androidx.core.content.withStyledAttributes
|
||||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.asImage
|
import coil3.asImage
|
||||||
import coil3.request.Disposable
|
import coil3.request.Disposable
|
||||||
@@ -25,10 +26,14 @@ import coil3.size.ViewSizeResolver
|
|||||||
import coil3.util.CoilUtils
|
import coil3.util.CoilUtils
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.isNetworkError
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -42,6 +47,9 @@ open class CoilImageView @JvmOverloads constructor(
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var networkState: NetworkState
|
||||||
|
|
||||||
var allowRgb565: Boolean = false
|
var allowRgb565: Boolean = false
|
||||||
var useExistingDrawable: Boolean = false
|
var useExistingDrawable: Boolean = false
|
||||||
var decodeRegion: Boolean = false
|
var decodeRegion: Boolean = false
|
||||||
@@ -54,9 +62,13 @@ open class CoilImageView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private var currentRequest: Disposable? = null
|
private var currentRequest: Disposable? = null
|
||||||
private var currentImageData: Any = NullRequestData
|
private var currentImageData: Any = NullRequestData
|
||||||
|
private var networkWaitingJob: Job? = null
|
||||||
|
|
||||||
private var listeners: MutableList<ImageRequest.Listener>? = null
|
private var listeners: MutableList<ImageRequest.Listener>? = null
|
||||||
|
|
||||||
|
val isFailed: Boolean
|
||||||
|
get() = CoilUtils.result(this) is ErrorResult
|
||||||
|
|
||||||
init {
|
init {
|
||||||
context.withStyledAttributes(attrs, R.styleable.CoilImageView, defStyleAttr) {
|
context.withStyledAttributes(attrs, R.styleable.CoilImageView, defStyleAttr) {
|
||||||
allowRgb565 = getBoolean(R.styleable.CoilImageView_allowRgb565, allowRgb565)
|
allowRgb565 = getBoolean(R.styleable.CoilImageView_allowRgb565, allowRgb565)
|
||||||
@@ -81,6 +93,9 @@ open class CoilImageView @JvmOverloads constructor(
|
|||||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
override fun onError(request: ImageRequest, result: ErrorResult) {
|
||||||
super.onError(request, result)
|
super.onError(request, result)
|
||||||
listeners?.forEach { it.onError(request, result) }
|
listeners?.forEach { it.onError(request, result) }
|
||||||
|
if (result.throwable.isNetworkError()) {
|
||||||
|
waitForNetwork()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart(request: ImageRequest) {
|
override fun onStart(request: ImageRequest) {
|
||||||
@@ -115,17 +130,27 @@ open class CoilImageView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun disposeImage() {
|
fun disposeImage() {
|
||||||
|
networkWaitingJob?.cancel()
|
||||||
|
networkWaitingJob = null
|
||||||
CoilUtils.dispose(this)
|
CoilUtils.dispose(this)
|
||||||
currentRequest = null
|
currentRequest = null
|
||||||
currentImageData = NullRequestData
|
currentImageData = NullRequestData
|
||||||
setImageDrawable(null)
|
setImageDrawable(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun enqueueRequest(request: ImageRequest): Disposable {
|
fun reload() {
|
||||||
|
CoilUtils.result(this)?.let { result ->
|
||||||
|
enqueueRequest(result.request, force = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun enqueueRequest(request: ImageRequest, force: Boolean = false): Disposable {
|
||||||
val previous = currentRequest
|
val previous = currentRequest
|
||||||
if (currentImageData == request.data && previous?.job?.isCancelled == false) {
|
if (!force && currentImageData == request.data && previous?.job?.isCancelled == false && !isFailed) {
|
||||||
return previous
|
return previous
|
||||||
}
|
}
|
||||||
|
networkWaitingJob?.cancel()
|
||||||
|
networkWaitingJob = null
|
||||||
currentImageData = request.data
|
currentImageData = request.data
|
||||||
return coil.enqueue(request).also { currentRequest = it }
|
return coil.enqueue(request).also { currentRequest = it }
|
||||||
}
|
}
|
||||||
@@ -175,4 +200,17 @@ open class CoilImageView @JvmOverloads constructor(
|
|||||||
} else {
|
} else {
|
||||||
Scale.FIT
|
Scale.FIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun waitForNetwork() {
|
||||||
|
if (networkWaitingJob?.isActive == true || networkState.isOnline()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
networkWaitingJob?.cancel()
|
||||||
|
networkWaitingJob = findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
|
||||||
|
networkState.awaitForConnection()
|
||||||
|
if (isFailed) {
|
||||||
|
reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
|||||||
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
|
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
|
import java.net.HttpURLConnection
|
||||||
import java.net.NoRouteToHostException
|
import java.net.NoRouteToHostException
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
@@ -216,6 +217,7 @@ fun Throwable.isNetworkError(): Boolean {
|
|||||||
|| this is SocketTimeoutException
|
|| this is SocketTimeoutException
|
||||||
|| this is StreamResetException
|
|| this is StreamResetException
|
||||||
|| this is SocketException
|
|| this is SocketException
|
||||||
|
|| this is HttpException && response.code == HttpURLConnection.HTTP_GATEWAY_TIMEOUT
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.report(silent: Boolean = false) {
|
fun Throwable.report(silent: Boolean = false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user