103 lines
3.1 KiB
Kotlin
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) |