We used to rely on an external service to fetch the images. But they are not available anymorepull/5135/head
@ -1,25 +1,69 @@ |
|||||||
package com.example.jetsnack.ui.components |
package com.example.jetsnack.ui.components |
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
import android.annotation.SuppressLint |
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.BitmapFactory |
||||||
|
import androidx.compose.animation.AnimatedContent |
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi |
||||||
|
import androidx.compose.animation.core.TweenSpec |
||||||
|
import androidx.compose.animation.fadeIn |
||||||
|
import androidx.compose.animation.fadeOut |
||||||
|
import androidx.compose.animation.with |
||||||
|
import androidx.compose.foundation.Image |
||||||
|
import androidx.compose.foundation.layout.Box |
||||||
import androidx.compose.runtime.Composable |
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.LaunchedEffect |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.runtime.setValue |
||||||
import androidx.compose.ui.Modifier |
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.graphics.ImageBitmap |
||||||
|
import androidx.compose.ui.graphics.asImageBitmap |
||||||
import androidx.compose.ui.layout.ContentScale |
import androidx.compose.ui.layout.ContentScale |
||||||
import androidx.compose.ui.platform.LocalContext |
import com.example.common.generated.resources.Res |
||||||
import androidx.compose.ui.res.painterResource |
import kotlinx.coroutines.Dispatchers |
||||||
import coil.compose.AsyncImage |
import kotlinx.coroutines.withContext |
||||||
import coil.request.ImageRequest |
import org.jetbrains.compose.resources.ExperimentalResourceApi |
||||||
import com.example.jetsnack.R |
|
||||||
|
|
||||||
|
private val imagesCache = mutableMapOf<String, ImageBitmap>() |
||||||
|
|
||||||
|
@SuppressLint("UnusedContentLambdaTargetStateParameter") |
||||||
|
@OptIn(ExperimentalAnimationApi::class, ExperimentalResourceApi::class) |
||||||
@Composable |
@Composable |
||||||
actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifier: Modifier) { |
actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifier: Modifier) { |
||||||
AsyncImage( |
var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } |
||||||
model = ImageRequest.Builder(LocalContext.current) |
|
||||||
.data(imageUrl) |
|
||||||
.crossfade(true) |
AnimatedContent(img, transitionSpec = { |
||||||
.build(), |
fadeIn(TweenSpec()) with fadeOut(TweenSpec()) |
||||||
contentDescription = contentDescription, |
}) { |
||||||
placeholder = painterResource(R.drawable.placeholder), |
if (img != null) { |
||||||
modifier = Modifier.fillMaxSize(), |
Image(img!!, contentDescription = contentDescription, modifier = modifier, contentScale = ContentScale.Crop) |
||||||
contentScale = ContentScale.Crop, |
} else { |
||||||
) |
Box(modifier = modifier) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
LaunchedEffect(imageUrl) { |
||||||
|
if (imagesCache.contains(imageUrl)) { |
||||||
|
img = imagesCache[imageUrl] |
||||||
|
} else { |
||||||
|
withContext(Dispatchers.IO) { |
||||||
|
img = try { |
||||||
|
Res.readBytes(imageUrl).toAndroidBitmap().asImageBitmap().also { |
||||||
|
imagesCache[imageUrl] = it |
||||||
|
img = it |
||||||
|
} |
||||||
|
} catch (e: Throwable) { |
||||||
|
e.printStackTrace() |
||||||
|
null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun ByteArray.toAndroidBitmap(): Bitmap { |
||||||
|
return BitmapFactory.decodeByteArray(this, 0, size) |
||||||
} |
} |
After Width: | Height: | Size: 332 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 382 KiB |
After Width: | Height: | Size: 653 KiB |
After Width: | Height: | Size: 496 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 380 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 250 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 217 KiB |
After Width: | Height: | Size: 218 KiB |
After Width: | Height: | Size: 135 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 187 KiB |