tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 80068c60193e1445912395116a92c1a2d670e286
parent af682319d9bccd38c7ad6f5e5f3899cc4a5737b3
Author: Gabriel Luong <gabriel.luong@gmail.com>
Date:   Wed,  3 Dec 2025 01:08:44 +0000

Bug 2003371 - Part 1: Remove old Pocket Stories Composables r=android-reviewers,devota

- Removes `PocketStoriesComposables` and the unused `TabSubtitleWithInterdot` that would've resulted from the removal.
- Cleans up the pocket stories fake to exclude `PocketStory` types that we are not supporting.

Differential Revision: https://phabricator.services.mozilla.com/D274667

Diffstat:
Dmobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/TabSubtitleWithInterdot.kt | 116-------------------------------------------------------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt | 1-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/fake/FakeHomepagePreview.kt | 51+++++----------------------------------------------
Dmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/PocketStoriesComposables.kt | 673-------------------------------------------------------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/Stories.kt | 176+++----------------------------------------------------------------------------
5 files changed, 10 insertions(+), 1007 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/TabSubtitleWithInterdot.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/compose/TabSubtitleWithInterdot.kt @@ -1,116 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.compose - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import org.mozilla.fenix.theme.FirefoxTheme - -/** - * Special caption text for a tab layout shown on one line. - * - * This will combine [firstText] with a interdot and then [secondText] ensuring that the second text - * (which is assumed to be smaller) always fills as much space as needed with the [firstText] automatically - * being resized to be smaller with an added ellipsis characters if needed. - * - * Possible results: - * ``` - * - when both texts would fit the screen - * ------------------------------------------ - * |firstText · secondText | - * ------------------------------------------ - * - * - when both text do not fit, second is shown in entirety, first is ellipsised. - * ------------------------------------------ - * |longerFirstTextOrSmallSc... · secondText| - * ------------------------------------------ - * ``` - * - * @param firstText Text shown at the start of the row. - * @param secondText Text shown at the end of the row. - */ -@Composable -fun TabSubtitleWithInterdot( - firstText: String, - secondText: String, -) { - val currentLayoutDirection = LocalLayoutDirection.current - - Layout( - content = { - Text( - text = firstText, - color = FirefoxTheme.colors.textSecondary, - fontSize = 12.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - Text( - text = " \u00b7 ", - color = FirefoxTheme.colors.textSecondary, - fontSize = 12.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - Text( - text = secondText, - color = FirefoxTheme.colors.textSecondary, - fontSize = 12.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - }, - ) { items, constraints -> - - // We need to measure from the end to start to ensure the secondItem will always be on screen - // and depending on secondItem's width and interdot's width the firstItem is automatically resized. - val secondItem = items[2].measure(constraints) - val interdot = items[1].measure( - constraints.copy(maxWidth = constraints.maxWidth - secondItem.width), - ) - val firstItem = items[0].measure( - constraints.copy(maxWidth = constraints.maxWidth - secondItem.width - interdot.width), - ) - - layout(constraints.maxWidth, constraints.maxHeight) { - val itemsPositions = IntArray(items.size) - with(Arrangement.Start) { - arrange( - constraints.maxWidth, - intArrayOf(firstItem.width, interdot.width, secondItem.width), - currentLayoutDirection, - itemsPositions, - ) - } - - val placementHeight = constraints.maxHeight - firstItem.height - listOf(firstItem, interdot, secondItem).forEachIndexed { index, item -> - item.place(itemsPositions[index], placementHeight) - } - } - } -} - -@Composable -@Preview -private fun TabSubtitleWithInterdotPreview() { - FirefoxTheme { - Box(Modifier.background(FirefoxTheme.colors.layer2)) { - TabSubtitleWithInterdot( - firstText = "firstText", - secondText = "secondText", - ) - } - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/AppState.kt @@ -15,7 +15,6 @@ import org.mozilla.fenix.components.appstate.AppState import org.mozilla.fenix.home.blocklist.BlocklistHandler import org.mozilla.fenix.home.pocket.POCKET_STORIES_DEFAULT_CATEGORY_NAME import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory -import org.mozilla.fenix.home.pocket.ui.PocketStory import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState import org.mozilla.fenix.utils.Settings diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/fake/FakeHomepagePreview.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/fake/FakeHomepagePreview.kt @@ -22,10 +22,6 @@ import mozilla.components.service.nimbus.messaging.MessageData import mozilla.components.service.nimbus.messaging.StyleData import mozilla.components.service.pocket.PocketStory import mozilla.components.service.pocket.PocketStory.ContentRecommendation -import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory -import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory -import mozilla.components.service.pocket.PocketStory.PocketSponsoredStoryCaps -import mozilla.components.service.pocket.PocketStory.PocketSponsoredStoryShim import mozilla.components.service.pocket.PocketStory.SponsoredContent import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode @@ -429,31 +425,6 @@ internal object FakeHomepagePreview { impressions = index.toLong(), ) - internal fun pocketRecommendedStory(index: Int = 0) = PocketRecommendedStory( - title = "Story - This is a ${"very ".repeat(index)} long title", - publisher = "Publisher", - url = "https://story$index.com", - imageUrl = URL, - timeToRead = index, - category = "Category #$index", - timesShown = index.toLong(), - ) - - internal fun pocketSponsoredStory(index: Int = 0) = PocketSponsoredStory( - id = index, - title = "This is a ${"very ".repeat(index)} long title", - url = "https://sponsored-story$index.com", - imageUrl = URL, - sponsor = "Mozilla", - shim = PocketSponsoredStoryShim("", ""), - priority = index, - caps = PocketSponsoredStoryCaps( - flightCount = index, - flightPeriod = index * 2, - lifetimeCount = index * 3, - ), - ) - internal fun sponsoredContent(index: Int = 0) = SponsoredContent( url = "https://sponsored-story$index.com", title = "This is a ${"very ".repeat(index)}long title", @@ -467,23 +438,11 @@ internal object FakeHomepagePreview { caps = PocketStory.SponsoredContentFrequencyCaps(flightPeriod = 1, flightCount = 0), ) - @Suppress("MagicNumber") - internal fun pocketStories(limit: Int = 5) = mutableListOf<PocketStory>().apply { - for (index in 0 until limit) { - when { - (index % 4 == 0) -> add( - sponsoredContent(index), - ) - (index % 3 == 0) -> add( - contentRecommendation(index), - ) - (index % 2 == 0) -> add( - pocketRecommendedStory(index), - ) - else -> add( - pocketSponsoredStory(index), - ) - } + internal fun pocketStories(limit: Int = 5) = (0 until limit).map { index -> + if (index % 2 == 0) { + sponsoredContent(index) + } else { + contentRecommendation(index) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/PocketStoriesComposables.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/PocketStoriesComposables.kt @@ -1,673 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -@file:Suppress("MagicNumber") - -package org.mozilla.fenix.home.pocket.ui - -import android.content.res.Configuration -import android.graphics.Rect -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.platform.LocalWindowInfo -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.text.style.TextOverflow -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 androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max -import androidx.core.net.toUri -import mozilla.components.compose.base.SelectableChip -import mozilla.components.compose.base.SelectableChipColors -import mozilla.components.compose.base.modifier.onShown -import mozilla.components.compose.base.utils.inComposePreview -import mozilla.components.service.pocket.PocketStory -import mozilla.components.service.pocket.PocketStory.ContentRecommendation -import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory -import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory -import mozilla.components.service.pocket.PocketStory.SponsoredContent -import org.mozilla.fenix.R -import org.mozilla.fenix.compose.ITEM_WIDTH -import org.mozilla.fenix.compose.ListItemTabSurface -import org.mozilla.fenix.compose.TabSubtitleWithInterdot -import org.mozilla.fenix.compose.eagerFlingBehavior -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.home.fake.FakeHomepagePreview -import org.mozilla.fenix.home.pocket.POCKET_STORIES_DEFAULT_CATEGORY_NAME -import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory -import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory -import org.mozilla.fenix.home.ui.HomepageTestTag.HOMEPAGE_SPONSORED_STORY -import org.mozilla.fenix.home.ui.HomepageTestTag.HOMEPAGE_STORY -import org.mozilla.fenix.theme.FirefoxTheme -import kotlin.math.roundToInt - -private const val URI_PARAM_UTM_KEY = "utm_source" -private const val POCKET_STORIES_UTM_VALUE = "pocket-newtab-android" - -/** - * Displays a single [PocketRecommendedStory]. - * - * @param story The [PocketRecommendedStory] to be displayed. - * @param backgroundColor The background [Color] of the story. - * @param onStoryClick Callback for when the user taps on this story. - */ -@Composable -fun PocketStory( - @PreviewParameter(PocketStoryProvider::class) story: PocketRecommendedStory, - backgroundColor: Color, - onStoryClick: (PocketRecommendedStory) -> Unit, -) { - val imageUrl = story.imageUrl.replace( - "{wh}", - with(LocalDensity.current) { "${116.dp.toPx().roundToInt()}x${84.dp.toPx().roundToInt()}" }, - ) - val isValidPublisher = story.publisher.isNotBlank() - val isValidTimeToRead = story.timeToRead >= 0 - ListItemTabSurface( - imageUrl = imageUrl, - backgroundColor = backgroundColor, - onClick = { onStoryClick(story) }, - ) { - Text( - text = story.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.story.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = FirefoxTheme.typography.body2, - ) - - if (isValidPublisher && isValidTimeToRead) { - TabSubtitleWithInterdot(story.publisher, "${story.timeToRead} min") - } else if (isValidPublisher) { - Text( - text = story.publisher, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.story.publisher" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } else if (isValidTimeToRead) { - Text( - text = "${story.timeToRead} min", - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.story.timeToRead" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } - } -} - -/** - * Displays a single [PocketSponsoredStory]. - * - * @param story The [PocketSponsoredStory] to be displayed. - * @param backgroundColor The background [Color] of the story. - * @param onStoryClick Callback for when the user taps on this story. - */ -@Composable -fun PocketSponsoredStory( - story: PocketSponsoredStory, - backgroundColor: Color, - onStoryClick: (PocketSponsoredStory) -> Unit, -) { - val (imageWidth, imageHeight) = with(LocalDensity.current) { - 116.dp.toPx().roundToInt() to 84.dp.toPx().roundToInt() - } - val imageUrl = story.imageUrl.replace( - "&resize=w[0-9]+-h[0-9]+".toRegex(), - "&resize=w$imageWidth-h$imageHeight", - ) - - ListItemTabSurface( - imageUrl = imageUrl, - contentPadding = PaddingValues(16.dp, 0.dp), - backgroundColor = backgroundColor, - onClick = { onStoryClick(story) }, - ) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Text( - text = story.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredStory.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = FirefoxTheme.typography.body2, - ) - - Text( - text = stringResource(R.string.pocket_stories_sponsor_indication), - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredStory.identifier" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - - Text( - text = story.sponsor, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredStory.sponsor" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } - } -} - -/** - * Displays a single [SponsoredContent]. - * - * @param sponsoredContent The [SponsoredContent] to be displayed. - * @param backgroundColor The background [Color] of the sponsored content. - * @param onClick Callback for when the user taps on the sponsored content. - */ -@Composable -fun SponsoredContent( - sponsoredContent: SponsoredContent, - backgroundColor: Color, - onClick: (SponsoredContent) -> Unit, -) { - ListItemTabSurface( - imageUrl = sponsoredContent.imageUrl, - imageContentScale = ContentScale.Crop, - contentPadding = PaddingValues(16.dp, 0.dp), - backgroundColor = backgroundColor, - onClick = { onClick(sponsoredContent) }, - ) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Text( - text = sponsoredContent.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredContent.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = FirefoxTheme.typography.body2, - ) - - Text( - text = stringResource(R.string.pocket_stories_sponsor_indication), - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredContent.identifier" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - - Text( - text = sponsoredContent.sponsor, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredContent.sponsor" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } - } -} - -/** - * Displays a single [ContentRecommendation]. - * - * @param recommendation The [ContentRecommendation] to be displayed. - * @param backgroundColor The background [Color] of the recommendation. - * @param onClick Callback for when the user taps on the recommendation. - */ -@Composable -fun ContentRecommendation( - recommendation: ContentRecommendation, - backgroundColor: Color, - onClick: (ContentRecommendation) -> Unit, -) { - val imageUrl = recommendation.imageUrl.replace( - "{wh}", - with(LocalDensity.current) { "${116.dp.toPx().roundToInt()}x${84.dp.toPx().roundToInt()}" }, - ) - - ListItemTabSurface( - imageUrl = imageUrl, - backgroundColor = backgroundColor, - onClick = { onClick(recommendation) }, - ) { - Text( - text = recommendation.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.contentRecommendation.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = FirefoxTheme.typography.body2, - ) - - Text( - text = recommendation.publisher, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.contentRecommendation.publisher" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } -} - -/** - * Displays a list of [PocketStory]es on 3 by 3 grid. - * If there aren't enough stories to fill all columns placeholders containing an external link - * to go to Pocket for more recommendations are added. - * - * @param stories The list of [PocketStory]ies to be displayed. Expect a list with 8 items. - * @param contentPadding Dimension for padding the content after it has been clipped. - * This space will be used for shadows and also content rendering when the list is scrolled. - * @param backgroundColor The background [Color] of each story. - * @param onStoryShown Callback for when a certain story is visible to the user. - * @param onStoryClicked Callback for when the user taps on a recommended story. - */ -@Suppress("CyclomaticComplexMethod", "LongMethod", "CognitiveComplexMethod") -@Composable -fun PocketStories( - @PreviewParameter(PocketStoryProvider::class) stories: List<PocketStory>, - contentPadding: Dp, - backgroundColor: Color = FirefoxTheme.colors.layer2, - onStoryShown: (PocketStory, Triple<Int, Int, Int>) -> Unit, - onStoryClicked: (PocketStory, Triple<Int, Int, Int>) -> Unit, -) { - // Show stories in at most 3 rows but on any number of columns depending on the data received. - val maxRowsNo = 1 - val storiesToShow = stories.chunked(maxRowsNo) - - val listState = rememberLazyListState() - val flingBehavior = eagerFlingBehavior(lazyRowState = listState) - - val configuration = LocalConfiguration.current - val screenWidth = with(LocalDensity.current) { - LocalWindowInfo.current.containerSize.width.toDp() - } - - val endPadding = - remember { mutableStateOf(endPadding(configuration, screenWidth, contentPadding)) } - // Force recomposition as padding is not consistently updated when orientation has changed. - endPadding.value = endPadding(configuration, screenWidth, contentPadding) - - LazyRow( - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.stories" - }, - contentPadding = PaddingValues(start = contentPadding, end = endPadding.value), - state = listState, - flingBehavior = flingBehavior, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - itemsIndexed(storiesToShow) { columnIndex, columnItems -> - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - columnItems.forEachIndexed { rowIndex, story -> - Box( - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = when (story) { - is PocketRecommendedStory, - is ContentRecommendation, - -> HOMEPAGE_STORY - - else -> HOMEPAGE_SPONSORED_STORY - } - }, - ) { - when (story) { - is PocketRecommendedStory -> { - PocketStory( - story = story, - backgroundColor = backgroundColor, - ) { - val uri = story.url.toUri() - .buildUpon() - .appendQueryParameter( - URI_PARAM_UTM_KEY, - POCKET_STORIES_UTM_VALUE, - ) - .build().toString() - onStoryClicked( - it.copy(url = uri), - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } - - is PocketSponsoredStory -> { - val screenBounds = Rect() - .apply { LocalView.current.getWindowVisibleDisplayFrame(this) } - .apply { - // Check if this is in a preview because `.settings()` breaks previews - if (!inComposePreview) { - val verticalOffset = LocalContext.current.settings().browserToolbarHeight - - if (LocalContext.current.settings().shouldUseBottomToolbar) { - bottom -= verticalOffset - } else { - top += verticalOffset - } - } - } - Box( - modifier = Modifier.onShown( - threshold = 0.5f, - onVisible = { - onStoryShown( - story, - Triple( - rowIndex, - columnIndex, - stories.indexOf(story), - ), - ) - }, - screenBounds = screenBounds, - ), - ) { - PocketSponsoredStory( - story = story, - backgroundColor = backgroundColor, - ) { - onStoryClicked( - story, - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } - } - - is ContentRecommendation -> { - ContentRecommendation( - recommendation = story, - backgroundColor = backgroundColor, - ) { - onStoryClicked( - story, - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } - - is SponsoredContent -> { - val screenBounds = Rect() - .apply { LocalView.current.getWindowVisibleDisplayFrame(this) } - .apply { - // Check if this is in a preview because `settings()` breaks previews - if (!inComposePreview) { - val verticalOffset = LocalContext.current.settings().browserToolbarHeight - - if (LocalContext.current.settings().shouldUseBottomToolbar) { - bottom -= verticalOffset - } else { - top += verticalOffset - } - } - } - - Box( - modifier = Modifier.onShown( - threshold = 0.5f, - onVisible = { - onStoryShown( - story, - Triple( - rowIndex, - columnIndex, - stories.indexOf(story), - ), - ) - }, - screenBounds = screenBounds, - ), - ) { - SponsoredContent( - sponsoredContent = story, - backgroundColor = backgroundColor, - ) { - onStoryClicked( - story, - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } - } - } - } - } - } - } - } -} - -private fun endPadding(configuration: Configuration, screenWidth: Dp, contentPadding: Dp) = - if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - alignColumnToTitlePadding(screenWidth = screenWidth, contentPadding = contentPadding) - } else { - contentPadding - } - -/** - * If the column item is wider than the [screenWidth] default to the [contentPadding]. - */ -private fun alignColumnToTitlePadding(screenWidth: Dp, contentPadding: Dp) = - max(screenWidth - (ITEM_WIDTH.dp + contentPadding), contentPadding) - -/** - * Displays a list of [PocketRecommendedStoriesCategory]s. - * - * @param categories The categories needed to be displayed. - * @param selections List of categories currently selected. - * @param modifier [Modifier] to be applied to the layout. - * @param categoryColors The color set defined by [SelectableChipColors] used to style Pocket categories. - * @param onCategoryClick Callback for when the user taps a category. - */ -@Composable -fun PocketStoriesCategories( - categories: List<PocketRecommendedStoriesCategory>, - selections: List<PocketRecommendedStoriesSelectedCategory>, - modifier: Modifier = Modifier, - categoryColors: SelectableChipColors = SelectableChipColors.buildColors(), - onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit, -) { - Box( - modifier = modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.categories" - }, - ) { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - categories.filter { it.name != POCKET_STORIES_DEFAULT_CATEGORY_NAME } - .forEach { category -> - SelectableChip( - text = category.name, - isSelected = selections.map { it.name }.contains(category.name), - selectableChipColors = categoryColors, - ) { - onCategoryClick(category) - } - } - } - } -} - -@Composable -@Preview -private fun PocketStoriesComposablesPreview() { - FirefoxTheme { - Box( - Modifier - .background(FirefoxTheme.colors.layer2) - .systemBarsPadding() - .padding(top = 32.dp), - ) { - Column { - PocketStories( - stories = FakeHomepagePreview.pocketStories(limit = 8), - contentPadding = 0.dp, - onStoryShown = { _, _ -> }, - onStoryClicked = { _, _ -> }, - ) - Spacer(Modifier.height(10.dp)) - - PocketStoriesCategories( - categories = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - .split(" ") - .map { PocketRecommendedStoriesCategory(it) }, - selections = emptyList(), - onCategoryClick = {}, - ) - } - } - } -} - -@Composable -@Preview -private fun PocketStoryPreview() { - FirefoxTheme { - Box( - Modifier - .fillMaxSize() - .background(FirefoxTheme.colors.layer2) - .padding(8.dp), - ) { - PocketStory( - story = FakeHomepagePreview.pocketRecommendedStory(), - backgroundColor = FirefoxTheme.colors.layer2, - ) {} - } - } -} - -@Composable -@Preview -private fun PocketSponsoredStoryPreview() { - FirefoxTheme { - Box( - Modifier - .fillMaxSize() - .background(FirefoxTheme.colors.layer2) - .padding(8.dp), - ) { - PocketSponsoredStory( - story = FakeHomepagePreview.pocketSponsoredStory(), - backgroundColor = FirefoxTheme.colors.layer2, - ) {} - } - } -} - -@Composable -@Preview -private fun ContentRecommendationPreview() { - FirefoxTheme { - Box( - Modifier - .fillMaxSize() - .background(FirefoxTheme.colors.layer2) - .padding(8.dp), - ) { - ContentRecommendation( - recommendation = FakeHomepagePreview.contentRecommendation(), - backgroundColor = FirefoxTheme.colors.layer2, - ) {} - } - } -} - -@Composable -@Preview -private fun SponsoredContentPreview() { - FirefoxTheme { - Box( - Modifier - .fillMaxSize() - .background(FirefoxTheme.colors.layer2) - .padding(8.dp), - ) { - SponsoredContent( - sponsoredContent = FakeHomepagePreview.sponsoredContent(), - backgroundColor = FirefoxTheme.colors.layer2, - ) {} - } - } -} - -private class PocketStoryProvider : PreviewParameterProvider<PocketStory> { - override val values = FakeHomepagePreview.pocketStories(limit = 7).asSequence() - override val count = 8 -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/Stories.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/pocket/ui/Stories.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.core.net.toUri import mozilla.components.compose.base.modifier.onShown import mozilla.components.compose.base.utils.inComposePreview import mozilla.components.service.pocket.PocketStory @@ -59,110 +58,11 @@ import org.mozilla.fenix.home.ui.HomepageTestTag.HOMEPAGE_STORY import org.mozilla.fenix.theme.FirefoxTheme import kotlin.math.roundToInt -private const val URI_PARAM_UTM_KEY = "utm_source" -private const val POCKET_STORIES_UTM_VALUE = "pocket-newtab-android" private const val DEFAULT_MAX_LINES = 3 private const val SPONSORED_MAX_LINES = 2 private const val ACCESSIBILITY_MAX_LINES_SCALE_FACTOR = 1.2f /** - * Displays a single [PocketRecommendedStory]. - * - * @param story The [PocketRecommendedStory] to be displayed. - * @param backgroundColor The background [Color] of the story. - * @param onStoryClick Callback for when the user taps on this story. - */ -@Composable -fun Story( - @PreviewParameter(StoryProvider::class) story: PocketRecommendedStory, - backgroundColor: Color, - onStoryClick: (PocketRecommendedStory) -> Unit, -) { - val imageUrl = story.imageUrl.replace( - "{wh}", - with(LocalDensity.current) { - "${IMAGE_SIZE.dp.toPx().roundToInt()}x${ - IMAGE_SIZE.dp.toPx().roundToInt() - }" - }, - ) - - ListItemTabSurface( - imageUrl = imageUrl, - backgroundColor = backgroundColor, - onClick = { onStoryClick(story) }, - ) { - Text( - text = story.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.story.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = maxLines(), - style = MaterialTheme.typography.bodyMedium, - ) - } -} - -/** - * Displays a single [PocketSponsoredStory]. - * - * @param story The [PocketSponsoredStory] to be displayed. - * @param backgroundColor The background [Color] of the story. - * @param onStoryClick Callback for when the user taps on this story. - */ -@Composable -fun SponsoredStory( - story: PocketSponsoredStory, - backgroundColor: Color, - onStoryClick: (PocketSponsoredStory) -> Unit, -) { - val (imageWidth, imageHeight) = with(LocalDensity.current) { - IMAGE_SIZE.dp.toPx().roundToInt() to IMAGE_SIZE.dp.toPx().roundToInt() - } - val imageUrl = story.imageUrl.replace( - "&resize=w[0-9]+-h[0-9]+".toRegex(), - "&resize=w$imageWidth-h$imageHeight", - ) - - ListItemTabSurface( - imageUrl = imageUrl, - backgroundColor = backgroundColor, - onClick = { onStoryClick(story) }, - ) { - Column( - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Text( - text = story.title, - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredStory.title" - }, - color = FirefoxTheme.colors.textPrimary, - overflow = TextOverflow.Ellipsis, - maxLines = maxSponsoredLines(), - style = FirefoxTheme.typography.body2, - ) - - Text( - text = stringResource(R.string.pocket_stories_sponsor_indication), - modifier = Modifier.semantics { - testTagsAsResourceId = true - testTag = "pocket.sponsoredStory.identifier" - }, - color = FirefoxTheme.colors.textSecondary, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = FirefoxTheme.typography.caption, - ) - } - } -} - -/** * Displays a single [SponsoredContent]. * * @param sponsoredContent The [SponsoredContent] to be displayed. @@ -299,69 +199,11 @@ fun Stories( }, ) { when (story) { - is PocketRecommendedStory -> { - Story( - story = story, - backgroundColor = backgroundColor, - ) { - val uri = story.url.toUri() - .buildUpon() - .appendQueryParameter( - URI_PARAM_UTM_KEY, - POCKET_STORIES_UTM_VALUE, - ) - .build().toString() - onStoryClicked( - it.copy(url = uri), - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } - - is PocketSponsoredStory -> { - val screenBounds = Rect() - .apply { LocalView.current.getWindowVisibleDisplayFrame(this) } - .apply { - // Check if this is in a preview because `.settings()` breaks previews - if (!inComposePreview) { - val verticalOffset = - with(LocalDensity.current) { - dimensionResource(id = R.dimen.browser_toolbar_height).roundToPx() - } - - if (LocalContext.current.settings().shouldUseBottomToolbar) { - bottom -= verticalOffset - } else { - top += verticalOffset - } - } - } - Box( - modifier = Modifier.onShown( - threshold = 0.5f, - onVisible = { - onStoryShown( - story, - Triple( - rowIndex, - columnIndex, - stories.indexOf(story), - ), - ) - }, - screenBounds = screenBounds, - ), - ) { - SponsoredStory( - story = story, - backgroundColor = backgroundColor, - ) { - onStoryClicked( - story, - Triple(rowIndex, columnIndex, stories.indexOf(story)), - ) - } - } + is PocketRecommendedStory, + is PocketSponsoredStory, + -> { + // no-op, don't handle these [PocketStory] types as they are no longer + // supported. } is ContentRecommendation -> { @@ -442,12 +284,8 @@ private fun StoriesWithCategoriesPreview() { Column { Stories( stories = listOf( - FakeHomepagePreview.pocketRecommendedStory(15), - FakeHomepagePreview.pocketSponsoredStory(15), FakeHomepagePreview.contentRecommendation(15), FakeHomepagePreview.sponsoredContent(15), - FakeHomepagePreview.pocketRecommendedStory(1), - FakeHomepagePreview.pocketSponsoredStory(1), FakeHomepagePreview.contentRecommendation(1), FakeHomepagePreview.sponsoredContent(1), ), @@ -482,12 +320,8 @@ private fun StoriesPreview() { ) { Stories( stories = listOf( - FakeHomepagePreview.pocketRecommendedStory(15), - FakeHomepagePreview.pocketSponsoredStory(15), FakeHomepagePreview.contentRecommendation(15), FakeHomepagePreview.sponsoredContent(15), - FakeHomepagePreview.pocketRecommendedStory(1), - FakeHomepagePreview.pocketSponsoredStory(1), FakeHomepagePreview.contentRecommendation(1), FakeHomepagePreview.sponsoredContent(1), ),