Limited images concurrent loading to 3

This commit is contained in:
Abdelilah El Aissaoui 2021-12-07 17:42:15 +01:00
parent bf6600878b
commit 7a3d68e3a6
6 changed files with 102 additions and 59 deletions

View File

@ -0,0 +1,15 @@
package app.extensions
import androidx.compose.runtime.Composable
import kotlinx.coroutines.sync.Semaphore
suspend inline fun Semaphore.acquireAndUse(
block: () -> Composable
) {
this.acquire()
try {
block()
} finally {
this.release()
}
}

View File

@ -1,6 +1,6 @@
package app.images package app.images
interface ImagesCache { interface ImagesCache {
fun getCachedObject(urlSource: String): ByteArray? fun getCachedImage(urlSource: String): ByteArray?
fun cacheImage(urlSource: String, image: ByteArray) fun cacheImage(urlSource: String, image: ByteArray)
} }

View File

@ -3,7 +3,7 @@ package app.images
object InMemoryImagesCache : ImagesCache { object InMemoryImagesCache : ImagesCache {
private val cachedImages = hashMapOf<String, ByteArray>() private val cachedImages = hashMapOf<String, ByteArray>()
override fun getCachedObject(urlSource: String): ByteArray? { override fun getCachedImage(urlSource: String): ByteArray? {
return cachedImages[urlSource] return cachedImages[urlSource]
} }

View File

@ -0,0 +1,75 @@
package app.images
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.res.useResource
import app.extensions.acquireAndUse
import app.extensions.toByteArray
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext
import org.jetbrains.skia.Image
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
suspend fun loadImageNetwork(url: String): ImageBitmap? = withContext(Dispatchers.IO) {
try {
val cachedImage = cache.getCachedImage(url)
if(cachedImage != null)
return@withContext cachedImage.toComposeImage()
loadingImagesSemaphore.acquireAndUse {
val imageByteArray = loadImage(url)
cache.cacheImage(url, imageByteArray)
return@withContext imageByteArray.toComposeImage()
}
} catch (ex: Exception) {
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 rememberNetworkImage(url: String): ImageBitmap {
val networkImageLoader = NetworkImageLoader
var image by remember(url) {
mutableStateOf(
useResource("image.jpg") {
Image.makeFromEncoded(it.toByteArray()).toComposeImageBitmap()
}
)
}
LaunchedEffect(url) {
val networkImage = networkImageLoader.loadImageNetwork(url)
if(networkImage != null)
image = networkImage
}
return image
}
fun ByteArray.toComposeImage() = Image.makeFromEncoded(this).toComposeImageBitmap()

View File

@ -5,38 +5,30 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.* import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.useResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.extensions.* import app.extensions.*
import kotlinx.coroutines.* import app.git.GitManager
import org.eclipse.jgit.diff.DiffEntry import app.images.rememberNetworkImage
import org.eclipse.jgit.revwalk.RevCommit
import app.theme.headerBackground import app.theme.headerBackground
import app.theme.headerText
import app.theme.primaryTextColor import app.theme.primaryTextColor
import app.theme.secondaryTextColor import app.theme.secondaryTextColor
import java.net.HttpURLConnection
import java.net.URL
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.toComposeImageBitmap
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.git.GitManager
import app.images.ImagesCache
import app.images.InMemoryImagesCache
import app.theme.headerText
import app.ui.components.TooltipText import app.ui.components.TooltipText
import org.eclipse.jgit.lib.PersonIdent import org.eclipse.jgit.diff.DiffEntry
import org.jetbrains.skia.Image.Companion.makeFromEncoded import org.eclipse.jgit.revwalk.RevCommit
@Composable @Composable
fun CommitChanges( fun CommitChanges(
@ -166,45 +158,6 @@ fun Author(commit: RevCommit) {
} }
} }
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 rememberNetworkImage(url: String, cache: ImagesCache = InMemoryImagesCache): ImageBitmap {
val cachedImage = cache.getCachedObject(url)
var image by remember(url) {
if(cachedImage != null)
mutableStateOf(makeFromEncoded(cachedImage).toComposeImageBitmap())
else
mutableStateOf(
useResource("image.jpg") {
makeFromEncoded(it.toByteArray()).toComposeImageBitmap()
}
)
}
if(cachedImage == null) {
LaunchedEffect(url) {
try {
loadImage(url).let {
image = makeFromEncoded(it).toComposeImageBitmap()
cache.cacheImage(url, it)
}
} catch (ex: Exception) {
println("Avatar loading failed: ${ex.message}")
}
}
}
return image
}
@Composable @Composable
fun CommitLogChanges(diffEntries: List<DiffEntry>, onDiffSelected: (DiffEntry) -> Unit) { fun CommitLogChanges(diffEntries: List<DiffEntry>, onDiffSelected: (DiffEntry) -> Unit) {
val selectedIndex = remember(diffEntries) { mutableStateOf(-1) } val selectedIndex = remember(diffEntries) { mutableStateOf(-1) }

View File

@ -36,6 +36,7 @@ import app.extensions.*
import app.git.GitManager import app.git.GitManager
import app.git.LogStatus import app.git.LogStatus
import app.git.graph.GraphNode import app.git.graph.GraphNode
import app.images.rememberNetworkImage
import app.theme.headerBackground import app.theme.headerBackground
import app.theme.headerText import app.theme.headerText
import app.theme.primaryTextColor import app.theme.primaryTextColor
@ -46,7 +47,6 @@ import app.ui.dialogs.MergeDialog
import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.NewBranchDialog
import app.ui.dialogs.NewTagDialog import app.ui.dialogs.NewTagDialog
import app.ui.dialogs.ResetBranchDialog import app.ui.dialogs.ResetBranchDialog
import app.ui.rememberNetworkImage
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit