For #26520 - Color homepage cards via wallpaper card colors

This commit is contained in:
Noah Bond 2022-10-25 14:33:14 -07:00 committed by mergify[bot]
parent 374bff84c4
commit f4e7471aea
25 changed files with 281 additions and 41 deletions

View File

@ -5,12 +5,14 @@
package org.mozilla.fenix.home.pocket
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@ -24,6 +26,7 @@ import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.home.HomeSectionHeader
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
import org.mozilla.fenix.wallpapers.WallpaperState
internal const val POCKET_CATEGORIES_SELECTED_AT_A_TIME_COUNT = 8
@ -55,12 +58,37 @@ class PocketCategoriesViewHolder(
val categoriesSelections = components.appStore
.observeAsComposableState { state -> state.pocketStoriesCategoriesSelections }.value
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
var selectedBackgroundColor: Color? = null
var unselectedBackgroundColor: Color? = null
var selectedTextColor: Color? = null
var unselectedTextColor: Color? = null
wallpaperState.composeRunIfWallpaperCardColorsAreAvailable { cardColorLight, cardColorDark ->
if (isSystemInDarkTheme()) {
selectedBackgroundColor = cardColorDark
unselectedBackgroundColor = cardColorLight
selectedTextColor = FirefoxTheme.colors.textActionPrimary
unselectedTextColor = FirefoxTheme.colors.textActionSecondary
} else {
selectedBackgroundColor = cardColorLight
unselectedBackgroundColor = cardColorDark
selectedTextColor = FirefoxTheme.colors.textActionSecondary
unselectedTextColor = FirefoxTheme.colors.textActionPrimary
}
}
// See the detailed comment in PocketStoriesViewHolder for reasoning behind this change.
if (!homeScreenReady) return
Column {
Spacer(Modifier.height(24.dp))
PocketTopics(
selectedBackgroundColor = selectedBackgroundColor,
unselectedBackgroundColor = unselectedBackgroundColor,
selectedTextColor = selectedTextColor,
unselectedTextColor = unselectedTextColor,
categories = categories ?: emptyList(),
categoriesSelections = categoriesSelections ?: emptyList(),
onCategoryClick = interactor::onCategoryClicked,
@ -75,6 +103,10 @@ class PocketCategoriesViewHolder(
@Composable
private fun PocketTopics(
selectedTextColor: Color? = null,
unselectedTextColor: Color? = null,
selectedBackgroundColor: Color? = null,
unselectedBackgroundColor: Color? = null,
categories: List<PocketRecommendedStoriesCategory> = emptyList(),
categoriesSelections: List<PocketRecommendedStoriesSelectedCategory> = emptyList(),
onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit,
@ -89,6 +121,10 @@ private fun PocketTopics(
PocketStoriesCategories(
categories = categories,
selections = categoriesSelections,
selectedTextColor = selectedTextColor,
unselectedTextColor = unselectedTextColor,
selectedBackgroundColor = selectedBackgroundColor,
unselectedBackgroundColor = unselectedBackgroundColor,
onCategoryClick = onCategoryClick,
modifier = Modifier
.fillMaxWidth(),

View File

@ -98,11 +98,13 @@ private val placeholderStory = PocketRecommendedStory("", "", "", "", "", 0, 0)
* 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(
@ -113,6 +115,7 @@ fun PocketStory(
val isValidTimeToRead = story.timeToRead >= 0
ListItemTabLarge(
imageUrl = imageUrl,
backgroundColor = backgroundColor,
onClick = { onStoryClick(story) },
title = {
Text(
@ -151,11 +154,13 @@ fun PocketStory(
* 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) {
@ -168,6 +173,7 @@ fun PocketSponsoredStory(
ListItemTabSurface(
imageUrl = imageUrl,
backgroundColor = backgroundColor,
onClick = { onStoryClick(story) },
) {
Text(
@ -208,14 +214,17 @@ fun PocketSponsoredStory(
* @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.
* @param onDiscoverMoreClicked Callback for when the user taps an element which contains an
*/
@Suppress("LongParameterList")
@Composable
fun PocketStories(
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketStory>,
contentPadding: Dp,
backgroundColor: Color = FirefoxTheme.colors.layer2,
onStoryShown: (PocketStory, Pair<Int, Int>) -> Unit,
onStoryClicked: (PocketStory, Pair<Int, Int>) -> Unit,
onDiscoverMoreClicked: (String) -> Unit,
@ -241,7 +250,10 @@ fun PocketStories(
onDiscoverMoreClicked("https://getpocket.com/explore?$POCKET_FEATURE_UTM_KEY_VALUE")
}
} else if (story is PocketRecommendedStory) {
PocketStory(story) {
PocketStory(
story = story,
backgroundColor = backgroundColor,
) {
val uri = Uri.parse(story.url)
.buildUpon()
.appendQueryParameter(URI_PARAM_UTM_KEY, POCKET_STORIES_UTM_VALUE)
@ -254,7 +266,10 @@ fun PocketStories(
onStoryShown(story, rowIndex to columnIndex)
},
) {
PocketSponsoredStory(story) {
PocketSponsoredStory(
story = story,
backgroundColor = backgroundColor,
) {
onStoryClicked(story, rowIndex to columnIndex)
}
}
@ -359,13 +374,22 @@ private fun Rect.getIntersectPercentage(realSize: IntSize, other: Rect): Float {
*
* @param categories The categories needed to be displayed.
* @param selections List of categories currently selected.
* @param selectedTextColor Text [Color] when the category is selected.
* @param unselectedTextColor Text [Color] when the category is not selected.
* @param selectedBackgroundColor Background [Color] when the category is selected.
* @param unselectedBackgroundColor Background [Color] when the category is not selected.
* @param onCategoryClick Callback for when the user taps a category.
* @param modifier [Modifier] to be applied to the layout.
*/
@Suppress("LongParameterList")
@Composable
fun PocketStoriesCategories(
categories: List<PocketRecommendedStoriesCategory>,
selections: List<PocketRecommendedStoriesSelectedCategory>,
selectedTextColor: Color? = null,
unselectedTextColor: Color? = null,
selectedBackgroundColor: Color? = null,
unselectedBackgroundColor: Color? = null,
onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit,
modifier: Modifier = Modifier,
) {
@ -375,7 +399,14 @@ fun PocketStoriesCategories(
verticalItemsSpacing = 16.dp,
) {
categories.filter { it.name != POCKET_STORIES_DEFAULT_CATEGORY_NAME }.forEach { category ->
SelectableChip(category.name, selections.map { it.name }.contains(category.name)) {
SelectableChip(
text = category.name,
isSelected = selections.map { it.name }.contains(category.name),
selectedTextColor = selectedTextColor,
unselectedTextColor = unselectedTextColor,
selectedBackgroundColor = selectedBackgroundColor,
unselectedBackgroundColor = unselectedBackgroundColor,
) {
onCategoryClick(category)
}
}

View File

@ -23,12 +23,12 @@ import androidx.recyclerview.widget.RecyclerView
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.R
import org.mozilla.fenix.R.dimen
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.home.HomeSectionHeader
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
import org.mozilla.fenix.wallpapers.WallpaperState
/**
* [RecyclerView.ViewHolder] for displaying the list of [PocketRecommendedStory]s from [AppStore].
@ -49,7 +49,7 @@ class PocketStoriesViewHolder(
@Composable
override fun Content() {
val horizontalPadding = dimensionResource(dimen.home_item_horizontal_margin)
val horizontalPadding = dimensionResource(R.dimen.home_item_horizontal_margin)
val homeScreenReady = components.appStore
.observeAsComposableState { state -> state.firstFrameDrawn }.value ?: false
@ -57,6 +57,9 @@ class PocketStoriesViewHolder(
val stories = components.appStore
.observeAsComposableState { state -> state.pocketStories }.value
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
/* This was originally done to address this perf issue:
* https://github.com/mozilla-mobile/fenix/issues/25545 for details.
* It was determined that Pocket content was becoming available before the first frame was
@ -88,11 +91,12 @@ class PocketStoriesViewHolder(
Spacer(Modifier.height(16.dp))
PocketStories(
stories ?: emptyList(),
horizontalPadding,
interactor::onStoryShown,
interactor::onStoryClicked,
interactor::onDiscoverMoreClicked,
stories = stories ?: emptyList(),
contentPadding = horizontalPadding,
backgroundColor = wallpaperState.wallpaperCardColor,
onStoryShown = interactor::onStoryShown,
onStoryClicked = interactor::onStoryClicked,
onDiscoverMoreClicked = interactor::onDiscoverMoreClicked,
)
}
}
@ -113,6 +117,7 @@ fun PocketStoriesViewHolderPreview() {
PocketStories(
stories = getFakePocketStories(8),
contentPadding = 0.dp,
backgroundColor = FirefoxTheme.colors.layer2,
onStoryShown = { _, _ -> },
onStoryClicked = { _, _ -> },
onDiscoverMoreClicked = {},

View File

@ -36,6 +36,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -64,6 +65,7 @@ private val imageModifier = Modifier
*
* @param bookmarks List of [RecentBookmark]s to display.
* @param menuItems List of [RecentBookmarksMenuItem] shown when long clicking a [RecentBookmarkItem]
* @param backgroundColor The background [Color] of each bookmark.
* @param onRecentBookmarkClick Invoked when the user clicks on a recent bookmark.
* @param onRecentBookmarkLongClick Invoked when the user long clicks on a recent bookmark.
*/
@ -71,6 +73,7 @@ private val imageModifier = Modifier
fun RecentBookmarks(
bookmarks: List<RecentBookmark>,
menuItems: List<RecentBookmarksMenuItem>,
backgroundColor: Color,
onRecentBookmarkClick: (RecentBookmark) -> Unit = {},
onRecentBookmarkLongClick: () -> Unit = {},
) {
@ -82,6 +85,7 @@ fun RecentBookmarks(
RecentBookmarkItem(
bookmark = bookmark,
menuItems = menuItems,
backgroundColor = backgroundColor,
onRecentBookmarkClick = onRecentBookmarkClick,
onRecentBookmarkLongClick = onRecentBookmarkLongClick,
)
@ -93,6 +97,8 @@ fun RecentBookmarks(
* A recent bookmark item.
*
* @param bookmark The [RecentBookmark] to display.
* @param menuItems The list of [RecentBookmarksMenuItem] shown when long clicking on the recent bookmark item.
* @param backgroundColor The background [Color] of the recent bookmark item.
* @param onRecentBookmarkClick Invoked when the user clicks on the recent bookmark item.
* @param onRecentBookmarkLongClick Invoked when the user long clicks on the recent bookmark item.
*/
@ -101,6 +107,7 @@ fun RecentBookmarks(
private fun RecentBookmarkItem(
bookmark: RecentBookmark,
menuItems: List<RecentBookmarksMenuItem>,
backgroundColor: Color,
onRecentBookmarkClick: (RecentBookmark) -> Unit = {},
onRecentBookmarkLongClick: () -> Unit = {},
) {
@ -118,7 +125,7 @@ private fun RecentBookmarkItem(
},
),
shape = cardShape,
backgroundColor = FirefoxTheme.colors.layer2,
backgroundColor = backgroundColor,
elevation = 6.dp,
) {
Column(
@ -277,6 +284,7 @@ private fun RecentBookmarksPreview() {
),
),
menuItems = listOf(),
backgroundColor = FirefoxTheme.colors.layer2,
)
}
}

View File

@ -15,6 +15,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
import org.mozilla.fenix.wallpapers.WallpaperState
import org.mozilla.fenix.GleanMetrics.RecentBookmarks as RecentBookmarksMetrics
class RecentBookmarksViewHolder(
@ -33,11 +34,13 @@ class RecentBookmarksViewHolder(
@Composable
override fun Content() {
val recentBookmarks = components.appStore
.observeAsComposableState { state -> state.recentBookmarks }
val recentBookmarks = components.appStore.observeAsComposableState { state -> state.recentBookmarks }
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
RecentBookmarks(
bookmarks = recentBookmarks.value ?: emptyList(),
backgroundColor = wallpaperState.wallpaperCardColor,
onRecentBookmarkClick = interactor::onRecentBookmarkClicked,
menuItems = listOf(
RecentBookmarksMenuItem(

View File

@ -34,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
@ -57,16 +58,22 @@ import org.mozilla.fenix.theme.Theme
* A recent synced tab card.
*
* @param tab The [RecentSyncedTab] to display.
* @param backgroundColor The background [Color] of the item.
* @param buttonBackgroundColor The background [Color] of the item's button.
* @param buttonTextColor The [Color] of the button's text.
* @param onRecentSyncedTabClick Invoked when the user clicks on the recent synced tab.
* @param onSeeAllSyncedTabsButtonClick Invoked when user clicks on the "See all" button in the synced tab card.
* @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option.
* @param onRecentSyncedTabLongClick Invoked when user long presses the recent synced tab.
*/
@OptIn(ExperimentalFoundationApi::class)
@Suppress("LongMethod")
@Suppress("LongMethod", "LongParameterList")
@Composable
fun RecentSyncedTab(
tab: RecentSyncedTab?,
backgroundColor: Color = FirefoxTheme.colors.layer2,
buttonBackgroundColor: Color = FirefoxTheme.colors.actionSecondary,
buttonTextColor: Color = FirefoxTheme.colors.textActionSecondary,
onRecentSyncedTabClick: (RecentSyncedTab) -> Unit,
onSeeAllSyncedTabsButtonClick: () -> Unit,
onRemoveSyncedTab: (RecentSyncedTab) -> Unit,
@ -91,7 +98,7 @@ fun RecentSyncedTab(
},
),
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
backgroundColor = backgroundColor,
elevation = 6.dp,
) {
Column(modifier = Modifier.padding(16.dp)) {
@ -178,12 +185,8 @@ fun RecentSyncedTab(
} else {
""
},
textColor = FirefoxTheme.colors.textActionSecondary,
backgroundColor = if (tab == null) {
FirefoxTheme.colors.layer3
} else {
FirefoxTheme.colors.actionSecondary
},
textColor = buttonTextColor,
backgroundColor = buttonBackgroundColor,
tint = FirefoxTheme.colors.iconActionSecondary,
onClick = onSeeAllSyncedTabsButtonClick,
)
@ -302,6 +305,7 @@ private fun LoadingRecentSyncedTab() {
FirefoxTheme(theme = Theme.getTheme()) {
RecentSyncedTab(
tab = null,
buttonBackgroundColor = FirefoxTheme.colors.layer3,
onRecentSyncedTabClick = {},
onSeeAllSyncedTabsButtonClick = {},
onRemoveSyncedTab = {},

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.home.recentsyncedtabs.view
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
@ -14,6 +15,9 @@ import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.WallpaperState
/**
* View holder for a recent synced tab item.
@ -42,8 +46,11 @@ class RecentSyncedTabViewHolder(
@Composable
override fun Content() {
val recentSyncedTabState =
components.appStore.observeAsComposableState { state -> state.recentSyncedTabState }
val recentSyncedTabState = components.appStore.observeAsComposableState { state -> state.recentSyncedTabState }
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
val isWallpaperNotDefault = !Wallpaper.nameIsDefault(wallpaperState.currentWallpaper.name)
recentSyncedTabState.value?.let {
val syncedTab = when (it) {
RecentSyncedTabState.None,
@ -51,8 +58,22 @@ class RecentSyncedTabViewHolder(
-> null
is RecentSyncedTabState.Success -> it.tabs.firstOrNull()
}
val buttonBackgroundColor = when {
syncedTab != null && isWallpaperNotDefault -> FirefoxTheme.colors.layer1
syncedTab != null -> FirefoxTheme.colors.actionSecondary
else -> FirefoxTheme.colors.layer3
}
val buttonTextColor = when {
wallpaperState.currentWallpaper.cardColorDark != null &&
isSystemInDarkTheme() -> FirefoxTheme.colors.textPrimary
else -> FirefoxTheme.colors.textActionSecondary
}
RecentSyncedTab(
tab = syncedTab,
backgroundColor = wallpaperState.wallpaperCardColor,
buttonBackgroundColor = buttonBackgroundColor,
buttonTextColor = buttonTextColor,
onRecentSyncedTabClick = recentSyncedTabInteractor::onRecentSyncedTabClicked,
onSeeAllSyncedTabsButtonClick = recentSyncedTabInteractor::onSyncedTabShowAllClicked,
onRemoveSyncedTab = recentSyncedTabInteractor::onRemovedRecentSyncedTab,

View File

@ -14,6 +14,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
import org.mozilla.fenix.wallpapers.WallpaperState
/**
* View holder for a recent tab item.
@ -42,9 +43,12 @@ class RecentTabViewHolder(
@Composable
override fun Content() {
val recentTabs = components.appStore.observeAsComposableState { state -> state.recentTabs }
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
RecentTabs(
recentTabs = recentTabs.value ?: emptyList(),
backgroundColor = wallpaperState.wallpaperCardColor,
onRecentTabClick = { recentTabInteractor.onRecentTabClicked(it) },
onRecentTabLongClick = { recentTabInteractor.onRecentTabLongClicked() },
menuItems = listOf(

View File

@ -39,6 +39,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
@ -66,12 +67,14 @@ import org.mozilla.fenix.theme.FirefoxTheme
*
* @param recentTabs List of [RecentTab] to display.
* @param menuItems List of [RecentTabMenuItem] shown long clicking a [RecentTab].
* @param backgroundColor The background [Color] of each item.
* @param onRecentTabClick Invoked when the user clicks on a recent tab.
*/
@Composable
fun RecentTabs(
recentTabs: List<RecentTab>,
menuItems: List<RecentTabMenuItem>,
backgroundColor: Color = FirefoxTheme.colors.layer2,
onRecentTabClick: (String) -> Unit = {},
onRecentTabLongClick: () -> Unit = {},
) {
@ -85,6 +88,7 @@ fun RecentTabs(
RecentTabItem(
tab = tab,
menuItems = menuItems,
backgroundColor = backgroundColor,
onRecentTabClick = onRecentTabClick,
onRecentTabLongClick = onRecentTabLongClick,
)
@ -98,6 +102,7 @@ fun RecentTabs(
* A recent tab item.
*
* @param tab [RecentTab.Tab] that was recently viewed.
* @param backgroundColor The background [Color] of the item.
* @param onRecentTabClick Invoked when the user clicks on a recent tab.
*/
@OptIn(ExperimentalFoundationApi::class)
@ -105,6 +110,7 @@ fun RecentTabs(
private fun RecentTabItem(
tab: RecentTab.Tab,
menuItems: List<RecentTabMenuItem>,
backgroundColor: Color,
onRecentTabClick: (String) -> Unit = {},
onRecentTabLongClick: () -> Unit = {},
) {
@ -123,7 +129,7 @@ private fun RecentTabItem(
},
),
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
backgroundColor = backgroundColor,
elevation = 6.dp,
) {
Row(

View File

@ -39,6 +39,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@ -64,6 +65,7 @@ private const val VISITS_PER_COLUMN = 3
*
* @param recentVisits List of [RecentlyVisitedItem] to display.
* @param menuItems List of [RecentVisitMenuItem] shown long clicking a [RecentlyVisitedItem].
* @param backgroundColor The background [Color] of each item.
* @param onRecentVisitClick Invoked when the user clicks on a recent visit.
* @param onRecentVisitLongClick Invoked when the user long clicks on a recent visit.
*/
@ -71,13 +73,14 @@ private const val VISITS_PER_COLUMN = 3
fun RecentlyVisited(
recentVisits: List<RecentlyVisitedItem>,
menuItems: List<RecentVisitMenuItem>,
backgroundColor: Color = FirefoxTheme.colors.layer2,
onRecentVisitClick: (RecentlyVisitedItem, Int) -> Unit = { _, _ -> },
onRecentVisitLongClick: () -> Unit = {},
) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
backgroundColor = backgroundColor,
elevation = 6.dp,
) {
val listState = rememberLazyListState()

View File

@ -20,6 +20,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryGroup
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryHighlight
import org.mozilla.fenix.home.recentvisits.interactor.RecentVisitsInteractor
import org.mozilla.fenix.wallpapers.WallpaperState
/**
* View holder for [RecentlyVisitedItem]s.
@ -43,6 +44,8 @@ class RecentlyVisitedViewHolder(
override fun Content() {
val recentVisits = components.appStore
.observeAsComposableState { state -> state.recentHistory }
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
RecentlyVisited(
recentVisits = recentVisits.value ?: emptyList(),
@ -59,6 +62,7 @@ class RecentlyVisitedViewHolder(
},
),
),
backgroundColor = wallpaperState.wallpaperCardColor,
onRecentVisitClick = { recentlyVisitedItem, pageNumber ->
when (recentlyVisitedItem) {
is RecentHistoryHighlight -> {

View File

@ -309,6 +309,7 @@ class SessionControlAdapter(
TopPlaceholderViewHolder.LAYOUT_ID -> TopPlaceholderViewHolder(view)
TopSitePagerViewHolder.LAYOUT_ID -> TopSitePagerViewHolder(
view = view,
store = components.appStore,
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor,
)

View File

@ -5,19 +5,27 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.button.Button
import org.mozilla.fenix.compose.button.TertiaryButton
import org.mozilla.fenix.home.sessioncontrol.CustomizeHomeIteractor
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.WallpaperState
class CustomizeHomeButtonViewHolder(
composeView: ComposeView,
@ -37,11 +45,31 @@ class CustomizeHomeButtonViewHolder(
@Composable
override fun Content() {
val wallpaperState = components.appStore
.observeAsComposableState { state -> state.wallpaperState }.value ?: WallpaperState.default
var buttonColor: Color = FirefoxTheme.colors.actionTertiary
val textColor: Color = FirefoxTheme.colors.textActionTertiary
wallpaperState.composeRunIfWallpaperCardColorsAreAvailable { cardColorLight, cardColorDark ->
buttonColor = if (isSystemInDarkTheme()) {
cardColorDark
} else {
cardColorLight
}
}
Column {
Spacer(modifier = Modifier.height(68.dp))
TertiaryButton(
/**
* This button will be stylized as a [TertiaryButton] when no wallpaper is selected.
* Otherwise, the background will use [Wallpaper.cardColorLight] or [Wallpaper.cardColorDark].
* */
Button(
text = stringResource(R.string.browser_menu_customize_home_1),
textColor = textColor,
backgroundColor = buttonColor,
tint = FirefoxTheme.colors.iconActionTertiary,
onClick = interactor::openCustomizeHomePage,
)
}

View File

@ -10,28 +10,36 @@ import android.view.View
import android.widget.PopupWindow
import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.databinding.TopSiteItemBinding
import org.mozilla.fenix.ext.bitmapForUrl
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.isSystemInDarkTheme
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.ext.name
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.view.ViewHolder
@SuppressLint("ClickableViewAccessibility")
class TopSiteItemViewHolder(
view: View,
store: AppStore,
private val viewLifecycleOwner: LifecycleOwner,
private val interactor: TopSiteInteractor,
) : ViewHolder(view) {
@ -63,12 +71,30 @@ class TopSiteItemViewHolder(
}
val menu = topSiteMenu.menuBuilder.build(view.context).show(anchor = it)
it.setOnTouchListener @SuppressLint("ClickableViewAccessibility") { v, event ->
it.setOnTouchListener { v, event ->
onTouchEvent(v, event, menu)
}
true
}
store.flowScoped(viewLifecycleOwner) { flow ->
flow.map { state -> state.wallpaperState }
.ifChanged()
.collect { currentState ->
var backgroundColor = ContextCompat.getColor(view.context, R.color.fx_mobile_layer_color_2)
currentState.runIfWallpaperCardColorsAreAvailable { cardColorLight, cardColorDark ->
backgroundColor = if (view.context.isSystemInDarkTheme()) {
cardColorDark
} else {
cardColorLight
}
}
binding.topSiteCard.setCardBackgroundColor(backgroundColor)
}
}
}
fun bind(topSite: TopSite, position: Int) {
@ -140,6 +166,7 @@ class TopSiteItemViewHolder(
Pings.topsitesImpression.submit()
}
@SuppressLint("ClickableViewAccessibility")
private fun onTouchEvent(
v: View,
event: MotionEvent,

View File

@ -12,18 +12,20 @@ import androidx.viewpager2.widget.ViewPager2
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.databinding.ComponentTopSitesPagerBinding
import org.mozilla.fenix.home.sessioncontrol.AdapterItem
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
class TopSitePagerViewHolder(
view: View,
store: AppStore,
viewLifecycleOwner: LifecycleOwner,
interactor: TopSiteInteractor,
) : RecyclerView.ViewHolder(view) {
private val binding = ComponentTopSitesPagerBinding.bind(view)
private val topSitesPagerAdapter = TopSitesPagerAdapter(viewLifecycleOwner, interactor)
private val topSitesPagerAdapter = TopSitesPagerAdapter(store, viewLifecycleOwner, interactor)
private val pageIndicator = binding.pageIndicator
private var currentPage = 0

View File

@ -9,17 +9,19 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.databinding.ComponentTopSitesBinding
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.utils.AccessibilityGridLayoutManager
class TopSiteViewHolder(
view: View,
store: AppStore,
viewLifecycleOwner: LifecycleOwner,
interactor: TopSiteInteractor,
) : RecyclerView.ViewHolder(view) {
private val topSitesAdapter = TopSitesAdapter(viewLifecycleOwner, interactor)
private val topSitesAdapter = TopSitesAdapter(store, viewLifecycleOwner, interactor)
val binding = ComponentTopSitesBinding.bind(view)
init {

View File

@ -10,17 +10,19 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.perf.StartupTimeline
class TopSitesAdapter(
private val store: AppStore,
private val viewLifecycleOwner: LifecycleOwner,
private val interactor: TopSiteInteractor,
) : ListAdapter<TopSite, TopSiteItemViewHolder>(TopSitesDiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteItemViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(TopSiteItemViewHolder.LAYOUT_ID, parent, false)
return TopSiteItemViewHolder(view, viewLifecycleOwner, interactor)
return TopSiteItemViewHolder(view, store, viewLifecycleOwner, interactor)
}
override fun onBindViewHolder(holder: TopSiteItemViewHolder, position: Int) {

View File

@ -11,11 +11,13 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.home.sessioncontrol.AdapterItem.TopSitePagerPayload
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.home.topsites.TopSitePagerViewHolder.Companion.TOP_SITES_PER_PAGE
class TopSitesPagerAdapter(
private val store: AppStore,
private val viewLifecycleOwner: LifecycleOwner,
private val interactor: TopSiteInteractor,
) : ListAdapter<List<TopSite>, TopSiteViewHolder>(TopSiteListDiffCallback) {
@ -23,7 +25,7 @@ class TopSitesPagerAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(TopSiteViewHolder.LAYOUT_ID, parent, false)
return TopSiteViewHolder(view, viewLifecycleOwner, interactor)
return TopSiteViewHolder(view, store, viewLifecycleOwner, interactor)
}
override fun onBindViewHolder(

View File

@ -4,6 +4,11 @@
package org.mozilla.fenix.wallpapers
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Represents all state related to the Wallpapers feature.
*
@ -20,4 +25,44 @@ data class WallpaperState(
availableWallpapers = listOf(),
)
}
/**
* Helper property used to obtain the [Color] to fill-in cards in front of a wallpaper.
*
* @return The appropriate light or dark wallpaper card [Color], if available, otherwise a default.
*/
val wallpaperCardColor: Color
@Composable get() = when {
currentWallpaper.cardColorLight != null && currentWallpaper.cardColorDark != null -> {
if (isSystemInDarkTheme()) {
Color(currentWallpaper.cardColorDark)
} else {
Color(currentWallpaper.cardColorLight)
}
}
else -> FirefoxTheme.colors.layer2
}
/**
* Run the Composable [run] block only if the current wallpaper's card colors are available.
*/
@Composable
fun composeRunIfWallpaperCardColorsAreAvailable(
run: @Composable (cardColorLight: Color, cardColorDark: Color) -> Unit,
) {
if (currentWallpaper.cardColorLight != null && currentWallpaper.cardColorDark != null) {
run(Color(currentWallpaper.cardColorLight), Color(currentWallpaper.cardColorDark))
}
}
/**
* Run the [run] block only if the current wallpaper's card colors are available.
*/
fun runIfWallpaperCardColorsAreAvailable(
run: (cardColorLight: Int, cardColorDark: Int) -> Unit,
) {
if (currentWallpaper.cardColorLight != null && currentWallpaper.cardColorDark != null) {
run(currentWallpaper.cardColorLight.toInt(), currentWallpaper.cardColorDark.toInt())
}
}
}

View File

@ -11,6 +11,7 @@
android:focusable="true">
<com.google.android.material.card.MaterialCardView
android:id="@+id/top_site_card"
style="@style/TopSite.Card">
<androidx.constraintlayout.widget.ConstraintLayout

View File

@ -682,7 +682,6 @@
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:importantForAccessibility">noHideDescendants</item>
<item name="contentPadding">@dimen/top_sites_card_padding</item>
<item name="cardBackgroundColor">?mozac_widget_favicon_background_color</item>
<item name="cardCornerRadius">@dimen/top_sites_card_radius</item>
<item name="cardElevation">@dimen/top_sites_card_elevation</item>
</style>

View File

@ -23,6 +23,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.databinding.TopSiteItemBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -37,6 +38,7 @@ class TopSiteItemViewHolderTest {
private lateinit var binding: TopSiteItemBinding
private lateinit var interactor: TopSiteInteractor
private lateinit var lifecycleOwner: LifecycleOwner
private lateinit var store: AppStore
private val pocket = TopSite.Default(
id = 1L,
@ -50,13 +52,14 @@ class TopSiteItemViewHolderTest {
binding = TopSiteItemBinding.inflate(LayoutInflater.from(testContext))
interactor = mockk(relaxed = true)
lifecycleOwner = mockk(relaxed = true)
store = mockk(relaxed = true)
every { testContext.components.core.icons } returns BrowserIcons(testContext, mockk(relaxed = true))
}
@Test
fun `calls interactor on click`() {
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).bind(pocket, position = 0)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).bind(pocket, position = 0)
binding.root.performClick()
verify { interactor.onSelectTopSite(pocket, position = 0) }
@ -65,7 +68,7 @@ class TopSiteItemViewHolderTest {
@Test
fun `calls interactor on long click`() {
every { testContext.components.analytics } returns mockk(relaxed = true)
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).bind(pocket, position = 0)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).bind(pocket, position = 0)
binding.root.performLongClick()
verify { interactor.onTopSiteMenuOpened() }
@ -80,7 +83,7 @@ class TopSiteItemViewHolderTest {
createdAt = 0,
)
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).bind(defaultTopSite, position = 0)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).bind(defaultTopSite, position = 0)
val pinIndicator = binding.topSiteTitle.compoundDrawables[0]
assertNotNull(pinIndicator)
@ -95,7 +98,7 @@ class TopSiteItemViewHolderTest {
createdAt = 0,
)
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).bind(pinnedTopSite, position = 0)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).bind(pinnedTopSite, position = 0)
val pinIndicator = binding.topSiteTitle.compoundDrawables[0]
assertNotNull(pinIndicator)
@ -110,7 +113,7 @@ class TopSiteItemViewHolderTest {
createdAt = 0,
)
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).bind(frecentTopSite, position = 0)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).bind(frecentTopSite, position = 0)
val pinIndicator = binding.topSiteTitle.compoundDrawables[0]
assertNull(pinIndicator)
@ -144,7 +147,7 @@ class TopSiteItemViewHolderTest {
topSiteImpressionSubmitted = true
}
TopSiteItemViewHolder(binding.root, lifecycleOwner, interactor).submitTopSitesImpressionPing(topSite, position)
TopSiteItemViewHolder(binding.root, store, lifecycleOwner, interactor).submitTopSitesImpressionPing(topSite, position)
assertNotNull(TopSites.contileImpression.testGetValue())

View File

@ -13,6 +13,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.databinding.ComponentTopSitesBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
@ -23,17 +24,19 @@ class TopSiteViewHolderTest {
private lateinit var binding: ComponentTopSitesBinding
private lateinit var lifecycleOwner: LifecycleOwner
private lateinit var interactor: TopSiteInteractor
private lateinit var store: AppStore
@Before
fun setup() {
binding = ComponentTopSitesBinding.inflate(LayoutInflater.from(testContext))
interactor = mockk(relaxed = true)
lifecycleOwner = mockk(relaxed = true)
store = mockk(relaxed = true)
}
@Test
fun `binds list of top sites`() {
TopSiteViewHolder(binding.root, lifecycleOwner, interactor).bind(
TopSiteViewHolder(binding.root, store, lifecycleOwner, interactor).bind(
listOf(
TopSite.Default(
id = 1L,

View File

@ -49,7 +49,7 @@ class TopSitesPagerAdapterTest {
@Before
fun setup() {
topSitesPagerAdapter = spyk(TopSitesPagerAdapter(mockk(), mockk()))
topSitesPagerAdapter = spyk(TopSitesPagerAdapter(mockk(), mockk(), mockk()))
}
@Test

View File

@ -53,7 +53,7 @@ class StartupReportFullyDrawnTest {
holderItemView = spyk(binding.root)
every { activity.findViewById<LinearLayout>(R.id.rootContainer) } returns rootContainer
every { holderItemView.context } returns activity
holder = TopSiteItemViewHolder(holderItemView, mockk(), mockk())
holder = TopSiteItemViewHolder(holderItemView, mockk(), mockk(), mockk())
every { rootContainer.viewTreeObserver } returns viewTreeObserver
every { holderItemView.viewTreeObserver } returns viewTreeObserver