[fenix] for https://github.com/mozilla-mobile/fenix/issues/24177: add ui wireframe for synced recent tab

pull/600/head
MatthewTighe 3 years ago committed by mergify[bot]
parent 13af68853e
commit da6d15fc81

@ -48,6 +48,21 @@ sealed class RecentTab {
*/
data class Tab(val state: TabSessionState) : RecentTab()
/**
* A tab that was recently viewed on a synced device.
*
* @param deviceDisplayName The device the tab was viewed on.
* @param title The title of the tab.
* @param url The url of the tab.
* @param previewImageUrl The url used to retrieve the preview image of the tab.
*/
data class SyncedTab(
val deviceDisplayName: String,
val title: String,
val url: String,
val previewImageUrl: String?,
) : RecentTab()
/**
* A search term group that was recently viewed
*

@ -46,7 +46,7 @@ class RecentTabViewHolder(
RecentTabs(
recentTabs = recentTabs.value ?: emptyList(),
onRecentTabClick = { interactor.onRecentTabClicked(it) },
onRecentSearchGroupClicked = { interactor.onRecentSearchGroupClicked(it) },
onRecentSearchGroupClick = { interactor.onRecentSearchGroupClicked(it) },
menuItems = listOf(
RecentTabMenuItem(
title = stringResource(id = R.string.recent_tab_menu_item_remove),

@ -2,7 +2,7 @@
* 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")
@file:Suppress("MagicNumber", "TooManyFunctions")
package org.mozilla.fenix.home.recenttabs.view
@ -16,6 +16,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
@ -26,6 +27,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Card
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
@ -50,6 +53,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -59,6 +63,7 @@ import mozilla.components.browser.icons.compose.WithIcon
import mozilla.components.concept.base.images.ImageLoadRequest
import mozilla.components.support.ktx.kotlin.getRepresentativeSnippet
import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.Image
@ -71,14 +76,19 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param recentTabs List of [RecentTab] to display.
* @param menuItems List of [RecentTabMenuItem] shown long clicking a [RecentTab].
* @param onRecentTabClick Invoked when the user clicks on a recent tab.
* @param onRecentSearchGroupClicked Invoked when the user clicks on a recent search group.
* @param onRecentSearchGroupClick Invoked when the user clicks on a recent search group.
* @param onRecentSyncedTabClick Invoked when the user clicks on the recent synced tab.
* @param onSyncedTabSeeAllButtonClick Invoked when user clicks on the "See all" button in the synced tab card.
*/
@Composable
@Suppress("LongParameterList")
fun RecentTabs(
recentTabs: List<RecentTab>,
menuItems: List<RecentTabMenuItem>,
onRecentTabClick: (String) -> Unit = {},
onRecentSearchGroupClicked: (String) -> Unit = {}
onRecentSearchGroupClick: (String) -> Unit = {},
onRecentSyncedTabClick: (RecentTab.SyncedTab) -> Unit = {},
onSyncedTabSeeAllButtonClick: () -> Unit = {},
) {
Column(
modifier = Modifier.fillMaxWidth(),
@ -99,7 +109,16 @@ fun RecentTabs(
searchTerm = tab.searchTerm,
tabId = tab.tabId,
count = tab.count,
onSearchGroupClicked = onRecentSearchGroupClicked
onSearchGroupClick = onRecentSearchGroupClick
)
}
}
is RecentTab.SyncedTab -> {
if (FeatureFlags.taskContinuityFeature) {
RecentSyncedTabItem(
tab,
onRecentSyncedTabClick,
onSyncedTabSeeAllButtonClick,
)
}
}
@ -158,9 +177,7 @@ private fun RecentTabItem(
Row {
RecentTabIcon(
url = tab.state.content.url,
modifier = Modifier
.size(18.dp, 18.dp)
.clip(RoundedCornerShape(2.dp)),
modifier = Modifier.size(18.dp).clip(RoundedCornerShape(2.dp)),
icon = tab.state.content.icon
)
@ -186,7 +203,7 @@ private fun RecentTabItem(
* @param searchTerm The search term for the group.
* @param tabId The id of the last accessed tab in the group.
* @param count Count of how many tabs belongs to the group.
* @param onSearchGroupClicked Invoked when the user clicks on a group.
* @param onSearchGroupClick Invoked when the user clicks on a group.
*/
@Suppress("LongParameterList")
@Composable
@ -194,13 +211,13 @@ private fun RecentSearchGroupItem(
searchTerm: String,
tabId: String,
count: Int,
onSearchGroupClicked: (String) -> Unit = {}
onSearchGroupClick: (String) -> Unit = {}
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(112.dp)
.clickable { onSearchGroupClicked(tabId) },
.clickable { onSearchGroupClick(tabId) },
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
elevation = 6.dp
@ -244,25 +261,139 @@ private fun RecentSearchGroupItem(
}
}
/**
* A recent synced tab.
*
* @param tab Optional synced tab. If null, displays placeholders.
* @param onRecentSyncedTabClick Invoked when item is clicked.
* @param onSeeAllButtonClick Invoked when "See all" button is clicked.
*/
@Suppress("LongMethod")
@Composable
private fun RecentSyncedTabItem(
tab: RecentTab.SyncedTab?,
onRecentSyncedTabClick: (RecentTab.SyncedTab) -> Unit,
onSeeAllButtonClick: () -> Unit,
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.clickable { tab?.let { onRecentSyncedTabClick(tab) } },
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2,
elevation = 6.dp
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
if (tab == null) {
RecentTabImagePlaceholder()
} else {
RecentTabImage(
tab = tab,
modifier = Modifier
.size(108.dp, 80.dp)
.clip(RoundedCornerShape(8.dp))
)
}
Spacer(modifier = Modifier.width(16.dp))
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxHeight()
) {
if (tab == null) {
RecentTabTitlePlaceholder()
} else {
RecentTabTitle(title = tab.title)
}
Row(verticalAlignment = Alignment.CenterVertically) {
if (tab == null) {
Box(
modifier = Modifier
.background(FirefoxTheme.colors.layer3)
.size(18.dp)
)
} else {
Image(
painter = painterResource(R.drawable.ic_synced_tabs),
contentDescription = stringResource(
R.string.recent_tabs_synced_device_icon_content_description
),
modifier = Modifier.size(18.dp, 18.dp)
)
}
Spacer(modifier = Modifier.width(8.dp))
if (tab == null) {
TextLinePlaceHolder()
} else {
RecentTabSubtitle(subtitle = tab.deviceDisplayName)
}
}
}
}
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = onSeeAllButtonClick,
colors = ButtonDefaults.outlinedButtonColors(
backgroundColor = if (tab == null) {
FirefoxTheme.colors.layer3
} else {
FirefoxTheme.colors.actionSecondary
}
),
elevation = ButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp
),
modifier = Modifier
.height(36.dp)
.fillMaxWidth()
) {
if (tab != null) {
Text(
text = stringResource(R.string.recent_tabs_see_all_synced_tabs_button_text),
textAlign = TextAlign.Center,
color = FirefoxTheme.colors.textActionSecondary
)
}
}
}
}
}
/**
* A recent tab image.
*
* @param tab [RecentTab.Tab] that was recently viewed.
* @param tab [RecentTab] that was recently viewed.
* @param modifier [Modifier] used to draw the image content.
* @param contentScale [ContentScale] used to draw image content.
* @param alignment [Alignment] used to draw the image content.
* is null.
*/
@Composable
@Suppress("LongParameterList")
private fun RecentTabImage(
tab: RecentTab.Tab,
tab: RecentTab,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.FillWidth,
alignment: Alignment = Alignment.TopCenter
) {
val previewImageUrl = tab.state.content.previewImageUrl
val thumbnail = tab.state.content.thumbnail
val (previewImageUrl, loaderUrl, key) = when (tab) {
is RecentTab.Tab -> Triple(
tab.state.content.previewImageUrl,
tab.state.content.url,
tab.state.id
)
is RecentTab.SyncedTab -> Triple(tab.previewImageUrl, tab.url, tab.url)
else -> return
}
val thumbnail = (tab as? RecentTab.Tab)?.state?.content?.thumbnail
when {
!previewImageUrl.isNullOrEmpty() -> {
@ -287,15 +418,10 @@ private fun RecentTabImage(
modifier = modifier,
backgroundColor = colorResource(id = R.color.photonGrey20)
) {
components.core.icons.Loader(tab.state.content.url) {
components.core.icons.Loader(loaderUrl) {
Placeholder {
Box(
modifier = Modifier.background(
color = when (isSystemInDarkTheme()) {
true -> PhotonColors.DarkGrey30
false -> PhotonColors.LightGrey30
}
)
modifier = Modifier.background(color = FirefoxTheme.colors.layer3)
)
}
@ -317,7 +443,7 @@ private fun RecentTabImage(
}
ThumbnailImage(
tabId = tab.state.id,
key = key,
modifier = modifier,
contentScale = contentScale,
alignment = alignment
@ -327,6 +453,19 @@ private fun RecentTabImage(
}
}
/**
* A placeholder for a recent tab image.
*/
@Composable
private fun RecentTabImagePlaceholder() {
Box(
modifier = Modifier
.size(108.dp, 80.dp)
.clip(RoundedCornerShape(8.dp))
.background(color = FirefoxTheme.colors.layer3)
)
}
/**
* Menu shown for a [RecentTab.Tab].
*
@ -444,6 +583,20 @@ private fun RecentTabTitle(title: String) {
)
}
/**
* A placeholder for a tab title.
*/
@Composable
private fun RecentTabTitlePlaceholder() {
Column {
TextLinePlaceHolder()
Spacer(modifier = Modifier.height(8.dp))
TextLinePlaceHolder()
}
}
/**
* A recent tab subtitle.
*
@ -465,18 +618,18 @@ private fun RecentTabSubtitle(subtitle: String) {
@Composable
private fun ThumbnailImage(
tabId: String,
key: String,
modifier: Modifier,
contentScale: ContentScale,
alignment: Alignment
) {
val rememberBitmap = remember(tabId) { mutableStateOf<ImageBitmap?>(null) }
val rememberBitmap = remember(key) { mutableStateOf<ImageBitmap?>(null) }
val size = LocalDensity.current.run { 108.dp.toPx().toInt() }
val request = ImageLoadRequest(tabId, size)
val request = ImageLoadRequest(key, size)
val storage = components.core.thumbnailStorage
val bitmap = rememberBitmap.value
LaunchedEffect(tabId) {
LaunchedEffect(key) {
rememberBitmap.value = storage.loadThumbnail(request).await()?.asImageBitmap()
}
@ -491,3 +644,13 @@ private fun ThumbnailImage(
)
}
}
@Composable
private fun TextLinePlaceHolder() {
Box(
modifier = Modifier
.height(12.dp)
.fillMaxWidth()
.background(FirefoxTheme.colors.layer3)
)
}

@ -127,6 +127,10 @@
<!-- Text for the number of tabs in a group in the 'Jump back in' section of the new tab
%d is a placeholder for the number of sites in the group. This number will always be more than one. -->
<string name="recent_tabs_search_term_count_2">%d sites</string>
<!-- Text for button in synced tab card that opens synced tabs tray -->
<string name="recent_tabs_see_all_synced_tabs_button_text">See all synced tabs</string>
<!-- Accessibility description for icon for recent synced tab -->
<string name="recent_tabs_synced_device_icon_content_description">Synced device</string>
<!-- Text for the menu button to remove a grouped highlight from the user's browsing history
in the Recently visited section -->
<string name="recent_tab_menu_item_remove">Remove</string>

Loading…
Cancel
Save