Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/images/NetworkImageLoader.kt
2022-10-22 15:35:32 +02:00

103 lines
3.1 KiB
Kotlin

package com.jetpackduba.gitnuro.images
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.res.useResource
import com.jetpackduba.gitnuro.extensions.acquireAndUse
import com.jetpackduba.gitnuro.extensions.toByteArray
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext
import org.jetbrains.skia.Image
import java.io.FileNotFoundException
import java.net.HttpURLConnection
import java.net.URL
private const val MAX_LOADING_IMAGES = 3
object NetworkImageLoader {
private val loadingImagesSemaphore = Semaphore(MAX_LOADING_IMAGES)
private val cache: ImagesCache = InMemoryImagesCache
fun loadCachedImage(url: String): ImageBitmap? {
val cachedImage = cache.getCachedImage(url)
return cachedImage?.toComposeImage()
}
suspend fun loadImageNetwork(url: String): ImageBitmap? = withContext(Dispatchers.IO) {
try {
val cachedImage = loadCachedImage(url)
if (cachedImage != null)
return@withContext cachedImage
loadingImagesSemaphore.acquireAndUse {
val imageByteArray = loadImage(url)
cache.cacheImage(url, imageByteArray)
return@withContext imageByteArray.toComposeImage()
}
} catch (ex: Exception) {
if (ex !is FileNotFoundException) {
// Commented as it fills the logs without useless info when there is no internet connection
//ex.printStackTrace()
}
}
// If a previous return hasn't been called, something has gone wrong, return null
return@withContext null
}
suspend fun loadImage(link: String): ByteArray = withContext(Dispatchers.IO) {
val url = URL(link)
val connection = url.openConnection() as HttpURLConnection
connection.connect()
connection.inputStream.toByteArray()
}
}
@Composable
fun rememberNetworkImageOrNull(url: String, placeHolderImageRes: String? = null): ImageBitmap? {
val networkImageLoader = NetworkImageLoader
val cacheImageUsed = remember { ValueHolder(false) }
var image by remember(url) {
val cachedImage = networkImageLoader.loadCachedImage(url)
val image: ImageBitmap? = when {
cachedImage != null -> {
cacheImageUsed.value = true
cachedImage
}
placeHolderImageRes != null -> useResource(placeHolderImageRes) {
Image.makeFromEncoded(it.toByteArray()).toComposeImageBitmap()
}
else -> null
}
return@remember mutableStateOf(image)
}
LaunchedEffect(url) {
if (!cacheImageUsed.value) {
val networkImage = NetworkImageLoader.loadImageNetwork(url)
if (networkImage != null && !cacheImageUsed.value) {
image = networkImage
}
}
}
return image
}
fun ByteArray.toComposeImage() = Image.makeFromEncoded(this).toComposeImageBitmap()
internal class ValueHolder<T>(var value: T)