Browse Source

Update images in Jetsnack example: host them locally and use Resources library (#5131)

We used to rely on an external service to fetch the images. But they are
not available anymore
pull/5135/head
Oleksandr Karpovich 2 months ago committed by GitHub
parent
commit
4c156252bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      examples/jetsnack/android/src/main/AndroidManifest.xml
  2. 1
      examples/jetsnack/common/build.gradle.kts
  3. 76
      examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt
  4. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/almonds.jpg
  5. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/apple_chips.jpg
  6. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/apple_juice.jpg
  7. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/apple_pie.jpg
  8. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/apple_sauce.jpg
  9. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/apples.jpg
  10. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/cheese.jpg
  11. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/chips.jpg
  12. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/cupcake.jpg
  13. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/desserts.jpg
  14. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/donut.jpg
  15. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/eclair.jpg
  16. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/froyo.jpg
  17. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/fruit.jpg
  18. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/gingerbread.jpg
  19. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/gluten_free.jpg
  20. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/grapes.jpg
  21. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/honeycomb.jpg
  22. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/ice_cream_sandwich.jpg
  23. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/jelly_bean.jpg
  24. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/kitkat.jpg
  25. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/kiwi.jpg
  26. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/lollipop.jpg
  27. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/mango.jpg
  28. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/marshmallow.jpg
  29. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/nougat.jpg
  30. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/nuts.jpg
  31. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/oreo.jpg
  32. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/organic.jpg
  33. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/paleo.jpg
  34. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/pie.jpg
  35. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/placeholder.jpg
  36. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/popcorn.jpg
  37. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/pretzels.jpg
  38. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/smoothies.jpg
  39. BIN
      examples/jetsnack/common/src/commonMain/composeResources/files/vegan.jpg
  40. 20
      examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt
  41. 57
      examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt
  42. 6
      examples/jetsnack/common/src/desktopMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt
  43. 35
      examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.ios.kt
  44. 16
      examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt
  45. 15
      examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt

2
examples/jetsnack/android/src/main/AndroidManifest.xml

@ -7,7 +7,7 @@
android:allowBackup="false" android:allowBackup="false"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name=".MainActivity" android:exported="true"> <activity android:name="com.example.android.MainActivity" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>

1
examples/jetsnack/common/build.gradle.kts

@ -39,6 +39,7 @@ kotlin {
api(compose.runtime) api(compose.runtime)
api(compose.foundation) api(compose.foundation)
api(compose.material) api(compose.material)
implementation(compose.components.resources)
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
} }
} }

76
examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt

@ -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)
} }

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/almonds.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/apple_chips.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/apple_juice.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/apple_pie.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/apple_sauce.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/apples.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/cheese.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/chips.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/cupcake.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/desserts.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/donut.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/eclair.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/froyo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/fruit.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/gingerbread.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/gluten_free.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/grapes.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/honeycomb.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/ice_cream_sandwich.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/jelly_bean.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/kitkat.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/kiwi.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/lollipop.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/mango.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/marshmallow.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/nougat.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/nuts.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/oreo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/organic.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/paleo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/pie.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/placeholder.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/popcorn.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/pretzels.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/smoothies.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
examples/jetsnack/common/src/commonMain/composeResources/files/vegan.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

20
examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt

@ -65,19 +65,19 @@ private val searchCategoryCollections = listOf(
categories = listOf( categories = listOf(
SearchCategory( SearchCategory(
name = "Chips & crackers", name = "Chips & crackers",
imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E" imageUrl = "files/chips.jpg"
), ),
SearchCategory( SearchCategory(
name = "Fruit snacks", name = "Fruit snacks",
imageUrl = "https://source.unsplash.com/SfP1PtM9Qa8" imageUrl = "files/fruit.jpg"
), ),
SearchCategory( SearchCategory(
name = "Desserts", name = "Desserts",
imageUrl = "https://source.unsplash.com/_jk8KIyN_uA" imageUrl = "files/desserts.jpg"
), ),
SearchCategory( SearchCategory(
name = "Nuts ", name = "Nuts ",
imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E" imageUrl = "files/nuts.jpg"
) )
) )
), ),
@ -87,27 +87,27 @@ private val searchCategoryCollections = listOf(
categories = listOf( categories = listOf(
SearchCategory( SearchCategory(
name = "Organic", name = "Organic",
imageUrl = "https://source.unsplash.com/7meCnGCJ5Ms" imageUrl = "files/organic.jpg"
), ),
SearchCategory( SearchCategory(
name = "Gluten Free", name = "Gluten Free",
imageUrl = "https://source.unsplash.com/m741tj4Cz7M" imageUrl = "files/gluten_free.jpg"
), ),
SearchCategory( SearchCategory(
name = "Paleo", name = "Paleo",
imageUrl = "https://source.unsplash.com/dt5-8tThZKg" imageUrl = "files/paleo.jpg"
), ),
SearchCategory( SearchCategory(
name = "Vegan", name = "Vegan",
imageUrl = "https://source.unsplash.com/ReXxkS1m1H0" imageUrl = "files/vegan.jpg"
), ),
SearchCategory( SearchCategory(
name = "Vegitarian", name = "Vegitarian",
imageUrl = "https://source.unsplash.com/IGfIGP5ONV0" imageUrl = "files/grapes.jpg"
), ),
SearchCategory( SearchCategory(
name = "Whole30", name = "Whole30",
imageUrl = "https://source.unsplash.com/9MzCd76xLGk" imageUrl = "files/popcorn.jpg"
) )
) )
) )

57
examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt

@ -17,6 +17,7 @@
package com.example.jetsnack.model package com.example.jetsnack.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.example.common.generated.resources.Res
@Immutable @Immutable
data class Snack( data class Snack(
@ -37,190 +38,190 @@ val snacks = listOf(
id = 1L, id = 1L,
name = "Cupcake", name = "Cupcake",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/pGM4sjt_BdQ", imageUrl = "files/cupcake.jpg",
price = 299 price = 299
), ),
Snack( Snack(
id = 2L, id = 2L,
name = "Donut", name = "Donut",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/Yc5sL-ejk6U", imageUrl = "files/donut.jpg",
price = 290 price = 290
), ),
Snack( Snack(
id = 3L, id = 3L,
name = "Eclair", name = "Eclair",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/-LojFX9NfPY", imageUrl = "files/eclair.jpg",
price = 289 price = 289
), ),
Snack( Snack(
id = 4L, id = 4L,
name = "Froyo", name = "Froyo",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/3U2V5WqK1PQ", imageUrl = "files/froyo.jpg",
price = 288 price = 288
), ),
Snack( Snack(
id = 5L, id = 5L,
name = "Gingerbread", name = "Gingerbread",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/Y4YR9OjdIMk", imageUrl = "files/gingerbread.jpg",
price = 499 price = 499
), ),
Snack( Snack(
id = 6L, id = 6L,
name = "Honeycomb", name = "Honeycomb",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/bELvIg_KZGU", imageUrl = "files/honeycomb.jpg",
price = 309 price = 309
), ),
Snack( Snack(
id = 7L, id = 7L,
name = "Ice Cream Sandwich", name = "Ice Cream Sandwich",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/YgYJsFDd4AU", imageUrl = "files/ice_cream_sandwich.jpg",
price = 1299 price = 1299
), ),
Snack( Snack(
id = 8L, id = 8L,
name = "Jellybean", name = "Jellybean",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/0u_vbeOkMpk", imageUrl = "files/jelly_bean.jpg",
price = 109 price = 109
), ),
Snack( Snack(
id = 9L, id = 9L,
name = "KitKat", name = "KitKat",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/yb16pT5F_jE", imageUrl = "files/kitkat.jpg",
price = 549 price = 549
), ),
Snack( Snack(
id = 10L, id = 10L,
name = "Lollipop", name = "Lollipop",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/AHF_ZktTL6Q", imageUrl = "files/lollipop.jpg",
price = 209 price = 209
), ),
Snack( Snack(
id = 11L, id = 11L,
name = "Marshmallow", name = "Marshmallow",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/rqFm0IgMVYY", imageUrl = "files/marshmallow.jpg",
price = 219 price = 219
), ),
Snack( Snack(
id = 12L, id = 12L,
name = "Nougat", name = "Nougat",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/qRE_OpbVPR8", imageUrl = "files/nougat.jpg",
price = 309 price = 309
), ),
Snack( Snack(
id = 13L, id = 13L,
name = "Oreo", name = "Oreo",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/33fWPnyN6tU", imageUrl = "files/oreo.jpg",
price = 339 price = 339
), ),
Snack( Snack(
id = 14L, id = 14L,
name = "Pie", name = "Pie",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/aX_ljOOyWJY", imageUrl = "files/pie.jpg",
price = 249 price = 249
), ),
Snack( Snack(
id = 15L, id = 15L,
name = "Chips", name = "Chips",
imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E", imageUrl = "files/chips.jpg",
price = 277 price = 277
), ),
Snack( Snack(
id = 16L, id = 16L,
name = "Pretzels", name = "Pretzels",
imageUrl = "https://source.unsplash.com/7meCnGCJ5Ms", imageUrl = "files/pretzels.jpg",
price = 154 price = 154
), ),
Snack( Snack(
id = 17L, id = 17L,
name = "Smoothies", name = "Smoothies",
imageUrl = "https://source.unsplash.com/m741tj4Cz7M", imageUrl = "files/smoothies.jpg",
price = 257 price = 257
), ),
Snack( Snack(
id = 18L, id = 18L,
name = "Popcorn", name = "Popcorn",
imageUrl = "https://source.unsplash.com/iuwMdNq0-s4", imageUrl = "files/popcorn.jpg",
price = 167 price = 167
), ),
Snack( Snack(
id = 19L, id = 19L,
name = "Almonds", name = "Almonds",
imageUrl = "https://source.unsplash.com/qgWWQU1SzqM", imageUrl = "files/almonds.jpg",
price = 123 price = 123
), ),
Snack( Snack(
id = 20L, id = 20L,
name = "Cheese", name = "Cheese",
imageUrl = "https://source.unsplash.com/9MzCd76xLGk", imageUrl = "files/cheese.jpg",
price = 231 price = 231
), ),
Snack( Snack(
id = 21L, id = 21L,
name = "Apples", name = "Apples",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/1d9xXWMtQzQ", imageUrl = "files/apples.jpg",
price = 221 price = 221
), ),
Snack( Snack(
id = 22L, id = 22L,
name = "Apple sauce", name = "Apple sauce",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/wZxpOw84QTU", imageUrl = "files/apple_sauce.jpg",
price = 222 price = 222
), ),
Snack( Snack(
id = 23L, id = 23L,
name = "Apple chips", name = "Apple chips",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/okzeRxm_GPo", imageUrl = "files/apple_chips.jpg",
price = 231 price = 231
), ),
Snack( Snack(
id = 24L, id = 24L,
name = "Apple juice", name = "Apple juice",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/l7imGdupuhU", imageUrl = "files/apple_juice.jpg",
price = 241 price = 241
), ),
Snack( Snack(
id = 25L, id = 25L,
name = "Apple pie", name = "Apple pie",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/bkXzABDt08Q", imageUrl = "files/apple_pie.jpg",
price = 225 price = 225
), ),
Snack( Snack(
id = 26L, id = 26L,
name = "Grapes", name = "Grapes",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/y2MeW00BdBo", imageUrl = "files/grapes.jpg",
price = 266 price = 266
), ),
Snack( Snack(
id = 27L, id = 27L,
name = "Kiwi", name = "Kiwi",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/1oMGgHn-M8k", imageUrl = "files/kiwi.jpg",
price = 127 price = 127
), ),
Snack( Snack(
id = 28L, id = 28L,
name = "Mango", name = "Mango",
tagline = "A tag line", tagline = "A tag line",
imageUrl = "https://source.unsplash.com/TIGDsyy0TK4", imageUrl = "files/mango.jpg",
price = 128 price = 128
) )
) )

6
examples/jetsnack/common/src/desktopMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt

@ -9,15 +9,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import com.example.common.generated.resources.Res
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.ExperimentalResourceApi
import java.net.URL import java.net.URL
import javax.imageio.ImageIO import javax.imageio.ImageIO
private val imagesCache = mutableMapOf<String, ImageBitmap>() private val imagesCache = mutableMapOf<String, ImageBitmap>()
@OptIn(ExperimentalAnimationApi::class) @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) {
var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) }
@ -39,7 +41,7 @@ actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifi
} else { } else {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
img = try { img = try {
ImageIO.read(URL(imageUrl)).toComposeImageBitmap().also { org.jetbrains.skia.Image.makeFromEncoded(Res.readBytes(imageUrl)).toComposeImageBitmap().also {
imagesCache[imageUrl] = it imagesCache[imageUrl] = it
img = it img = it
} }

35
examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.ios.kt

@ -9,10 +9,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import com.example.common.generated.resources.Res
import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned import kotlinx.cinterop.usePinned
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.skia.Image import org.jetbrains.skia.Image
import platform.Foundation.* import platform.Foundation.*
import platform.posix.memcpy import platform.posix.memcpy
@ -21,7 +23,7 @@ import kotlin.coroutines.resumeWithException
private val imagesCache = mutableMapOf<String, ImageBitmap>() private val imagesCache = mutableMapOf<String, ImageBitmap>()
@OptIn(ExperimentalAnimationApi::class) @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) {
var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) }
@ -43,7 +45,7 @@ actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifi
} else { } else {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
img = try { img = try {
loadImage(imageUrl).also { Image.makeFromEncoded(Res.readBytes(imageUrl)).toComposeImageBitmap().also {
imagesCache[imageUrl] = it imagesCache[imageUrl] = it
img = it img = it
} }
@ -54,33 +56,4 @@ actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifi
} }
} }
} }
}
@OptIn(ExperimentalForeignApi::class)
suspend fun loadImage(url: String): ImageBitmap = suspendCancellableCoroutine { continuation ->
val nsUrl = NSURL(string = url)
val task = NSURLSession.sharedSession.dataTaskWithURL(nsUrl) { data, response, error ->
if (data != null) {
val byteArray = ByteArray(data.length.toInt()).apply {
usePinned {
memcpy(
it.addressOf(0),
data.bytes,
data.length
)
}
}
continuation.resume(Image.makeFromEncoded(byteArray).toComposeImageBitmap())
} else {
error?.let {
continuation.resumeWithException(Exception(it.localizedDescription))
}
}
}
task.resume()
continuation.invokeOnCancellation {
task.cancel()
}
} }

16
examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt

@ -6,11 +6,14 @@ import androidx.compose.foundation.Image
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import com.example.common.generated.resources.Res
import kotlinx.coroutines.* import kotlinx.coroutines.*
import com.example.jetsnack.model.snacks import com.example.jetsnack.model.snacks
import org.jetbrains.compose.resources.ExperimentalResourceApi
val imagesCache = mutableMapOf<String, ImageBitmap>() val imagesCache = mutableMapOf<String, ImageBitmap>()
@OptIn(ExperimentalResourceApi::class)
@Composable @Composable
actual fun SnackAsyncImage( actual fun SnackAsyncImage(
imageUrl: String, imageUrl: String,
@ -28,21 +31,22 @@ actual fun SnackAsyncImage(
if (imagesCache.contains(imageUrl)) { if (imagesCache.contains(imageUrl)) {
bitmap = imagesCache[imageUrl]!! bitmap = imagesCache[imageUrl]!!
} else { } else {
val arrayBuffer = loadImage(imageUrl) imagesCache[imageUrl] = org.jetbrains.skia.Image.makeFromEncoded(
val skiaImg = org.jetbrains.skia.Image.makeFromEncoded(arrayBuffer.toByteArray()) Res.readBytes(imageUrl)
imagesCache[imageUrl] = skiaImg.toComposeImageBitmap() ).toComposeImageBitmap()
bitmap = imagesCache[imageUrl] bitmap = imagesCache[imageUrl]
} }
} }
} }
@OptIn(ExperimentalResourceApi::class)
suspend fun CoroutineScope.prepareImagesCache() { suspend fun CoroutineScope.prepareImagesCache() {
val jobs = mutableListOf<Job>() val jobs = mutableListOf<Job>()
// We have not many images, so we can prepare and cache them upfront // We have not many images, so we can prepare and cache them upfront
snacks.forEach { snacks.forEach {
val j = launch { val j = launch {
val arrayBuffer = loadImage(it.imageUrl) imagesCache[it.imageUrl] = org.jetbrains.skia.Image.makeFromEncoded(
val skiaImg = org.jetbrains.skia.Image.makeFromEncoded(arrayBuffer.toByteArray()) Res.readBytes(it.imageUrl)
imagesCache[it.imageUrl] = skiaImg.toComposeImageBitmap() ).toComposeImageBitmap()
} }
jobs.add(j) jobs.add(j)
} }

15
examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt

@ -13,6 +13,8 @@ import com.example.jetsnack.ui.components.loadImage
import com.example.jetsnack.ui.components.toByteArray import com.example.jetsnack.ui.components.toByteArray
import com.example.jetsnack.ui.theme.Karla import com.example.jetsnack.ui.theme.Karla
import com.example.jetsnack.ui.theme.Montserrat import com.example.jetsnack.ui.theme.Montserrat
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.* import org.jetbrains.compose.resources.*
@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class) @OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class)
@ -31,9 +33,16 @@ fun main() {
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
loadMontserratFont() val j1 = launch {
loadKarlaFont() loadMontserratFont()
prepareImagesCache() }
val j2 = launch {
loadKarlaFont()
}
val j3 = launch {
prepareImagesCache()
}
joinAll(j1, j2, j3)
loading = false loading = false
} }
} }

Loading…
Cancel
Save