diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index f0cf1f5fbb..8787187245 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -272,7 +272,8 @@ class SessionControlAdapter( when (viewType) { PocketStoriesViewHolder.LAYOUT_ID -> return PocketStoriesViewHolder( ComposeView(parent.context), - store + store, + components.core.client ) } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesComposables.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesComposables.kt index 58f3fd3d2f..e8fd759e92 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesComposables.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesComposables.kt @@ -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 + @PreviewParameter(PocketStoryProvider::class) stories: List, + 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 { } } } + +private class FakeClient : Client() { + override fun fetch(request: Request) = Response( + url = request.url, + status = 200, + body = Response.Body.empty(), + headers = MutableHeaders() + ) +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesViewHolder.kt index d789903d58..1888d285eb 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/pocket/PocketStoriesViewHolder.kt @@ -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 + ) } } }