For #21120 - ImageLoader with a shimmer effect placeholder

upstream-sync
Mugurell 3 years ago committed by mergify[bot]
parent abc881f9f8
commit a54a4ea20a

@ -272,7 +272,8 @@ class SessionControlAdapter(
when (viewType) {
PocketStoriesViewHolder.LAYOUT_ID -> return PocketStoriesViewHolder(
ComposeView(parent.context),
store
store,
components.core.client
)
}

@ -10,8 +10,10 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.pocket
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -43,6 +45,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -52,8 +55,18 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.MutableHeaders
import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.Response
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.support.images.compose.loader.Fallback
import mozilla.components.support.images.compose.loader.ImageLoader
import mozilla.components.support.images.compose.loader.Placeholder
import mozilla.components.support.images.compose.loader.WithImage
import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.R
import kotlin.math.roundToInt
import kotlin.random.Random
/**
@ -62,6 +75,7 @@ import kotlin.random.Random
@Composable
fun PocketStory(
@PreviewParameter(PocketStoryProvider::class) story: PocketRecommendedStory,
client: Client,
modifier: Modifier = Modifier
) {
Column(
@ -73,20 +87,53 @@ fun PocketStory(
Card(
elevation = 6.dp,
shape = RoundedCornerShape(4.dp),
modifier = Modifier
.size(160.dp, 87.dp)
.padding(bottom = 8.dp)
modifier = Modifier.size(160.dp, 87.dp)
) {
// Don't yet have a easy way to load URLs in Images.
// Default to a solid color to make it easy to appreciate dimensions
Box(Modifier.background(Color.Blue))
// Image(
// painterResource(R.drawable.ic_pdd),
// contentDescription = "hero image",
// contentScale = ContentScale.FillHeight,
// )
ImageLoader(
client = client,
// The endpoint allows us to ask for the optimal resolution image.
url = story.imageUrl.replace(
"{wh}",
with(LocalDensity.current) {
"${160.dp.toPx().roundToInt()}x${87.dp.toPx().roundToInt()}"
}
),
targetSize = 160.dp
) {
WithImage { painter ->
Image(
painter,
modifier = Modifier.size(160.dp, 87.dp),
contentDescription = "${story.title} story image"
)
}
Placeholder {
Box(
Modifier.background(
when (isSystemInDarkTheme()) {
true -> Color(0xFF42414D) // DarkGrey30
false -> PhotonColors.LightGrey30
}
)
)
}
Fallback {
Box(
Modifier.background(
when (isSystemInDarkTheme()) {
true -> Color(0xFF42414D) // DarkGrey30
false -> PhotonColors.LightGrey30
}
)
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
modifier = Modifier.padding(bottom = 2.dp),
@ -110,7 +157,8 @@ fun PocketStory(
*/
@Composable
fun PocketStories(
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketRecommendedStory>
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketRecommendedStory>,
client: Client
) {
// Items will be shown on two rows. Ceil the divide result to show more items on the top row.
val halfStoriesIndex = (stories.size + 1) / 2
@ -121,12 +169,12 @@ fun PocketStories(
Column(
Modifier.padding(end = if (index == halfStoriesIndex) 0.dp else 8.dp)
) {
PocketStory(item)
PocketStory(item, client)
Spacer(modifier = Modifier.height(24.dp))
stories.getOrNull(halfStoriesIndex + index)?.let {
PocketStory(it)
PocketStory(it, client)
}
}
}
@ -170,15 +218,6 @@ fun PocketRecommendations(
) {
content()
// Image(
// painterResource(R.drawable.ic_firefox_pocket),
// "Firefox and Pocket logos",
// Modifier
// .size(64.dp, 27.dp)
// .padding(top = 16.dp),
// contentScale = ContentScale.FillHeight
// )
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
ClickableText(
text = annotatedText,
@ -245,7 +284,10 @@ fun ExpandableCard(content: @Composable (() -> Unit)) {
private fun FinalDesign() {
ExpandableCard {
PocketRecommendations {
PocketStories(stories = getFakePocketStories(7))
PocketStories(
stories = getFakePocketStories(7),
client = FakeClient()
)
}
}
}
@ -273,3 +315,12 @@ private fun getFakePocketStories(limit: Int = 1): List<PocketRecommendedStory> {
}
}
}
private class FakeClient : Client() {
override fun fetch(request: Request) = Response(
url = request.url,
status = 200,
body = Response.Body.empty(),
headers = MutableHeaders()
)
}

@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.concept.fetch.Client
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.service.pocket.PocketRecommendedStory
import org.mozilla.fenix.home.HomeFragmentStore
@ -24,7 +25,8 @@ private const val STORIES_TO_SHOW_COUNT = 7
*/
class PocketStoriesViewHolder(
val composeView: ComposeView,
val store: HomeFragmentStore
val store: HomeFragmentStore,
val client: Client
) : RecyclerView.ViewHolder(composeView) {
init {
@ -32,7 +34,7 @@ class PocketStoriesViewHolder(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
composeView.setContent {
PocketStories(store)
PocketStories(store, client)
}
}
@ -42,14 +44,20 @@ class PocketStoriesViewHolder(
}
@Composable
fun PocketStories(store: HomeFragmentStore) {
fun PocketStories(
store: HomeFragmentStore,
client: Client
) {
val stories = store
.observeAsComposableState { state -> state.pocketArticles }.value
?.take(STORIES_TO_SHOW_COUNT)
ExpandableCard {
PocketRecommendations {
PocketStories(stories ?: emptyList())
PocketStories(
stories ?: emptyList(),
client
)
}
}
}

Loading…
Cancel
Save