2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-17 15:26:23 +00:00

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

This commit is contained in:
MatthewTighe 2022-03-11 14:11:01 -08:00 committed by mergify[bot]
parent 13af68853e
commit da6d15fc81
4 changed files with 210 additions and 28 deletions

View File

@ -48,6 +48,21 @@ sealed class RecentTab {
*/ */
data class Tab(val state: TabSessionState) : 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 * A search term group that was recently viewed
* *

View File

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

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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, 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 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight 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.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape 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.Card
import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem 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.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.concept.base.images.ImageLoadRequest
import mozilla.components.support.ktx.kotlin.getRepresentativeSnippet import mozilla.components.support.ktx.kotlin.getRepresentativeSnippet
import mozilla.components.ui.colors.PhotonColors import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.components import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.Image import org.mozilla.fenix.compose.Image
@ -71,14 +76,19 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param recentTabs List of [RecentTab] to display. * @param recentTabs List of [RecentTab] to display.
* @param menuItems List of [RecentTabMenuItem] shown long clicking a [RecentTab]. * @param menuItems List of [RecentTabMenuItem] shown long clicking a [RecentTab].
* @param onRecentTabClick Invoked when the user clicks on a recent tab. * @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 @Composable
@Suppress("LongParameterList")
fun RecentTabs( fun RecentTabs(
recentTabs: List<RecentTab>, recentTabs: List<RecentTab>,
menuItems: List<RecentTabMenuItem>, menuItems: List<RecentTabMenuItem>,
onRecentTabClick: (String) -> Unit = {}, onRecentTabClick: (String) -> Unit = {},
onRecentSearchGroupClicked: (String) -> Unit = {} onRecentSearchGroupClick: (String) -> Unit = {},
onRecentSyncedTabClick: (RecentTab.SyncedTab) -> Unit = {},
onSyncedTabSeeAllButtonClick: () -> Unit = {},
) { ) {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -99,7 +109,16 @@ fun RecentTabs(
searchTerm = tab.searchTerm, searchTerm = tab.searchTerm,
tabId = tab.tabId, tabId = tab.tabId,
count = tab.count, 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 { Row {
RecentTabIcon( RecentTabIcon(
url = tab.state.content.url, url = tab.state.content.url,
modifier = Modifier modifier = Modifier.size(18.dp).clip(RoundedCornerShape(2.dp)),
.size(18.dp, 18.dp)
.clip(RoundedCornerShape(2.dp)),
icon = tab.state.content.icon icon = tab.state.content.icon
) )
@ -186,7 +203,7 @@ private fun RecentTabItem(
* @param searchTerm The search term for the group. * @param searchTerm The search term for the group.
* @param tabId The id of the last accessed tab in 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 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") @Suppress("LongParameterList")
@Composable @Composable
@ -194,13 +211,13 @@ private fun RecentSearchGroupItem(
searchTerm: String, searchTerm: String,
tabId: String, tabId: String,
count: Int, count: Int,
onSearchGroupClicked: (String) -> Unit = {} onSearchGroupClick: (String) -> Unit = {}
) { ) {
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(112.dp) .height(112.dp)
.clickable { onSearchGroupClicked(tabId) }, .clickable { onSearchGroupClick(tabId) },
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.layer2, backgroundColor = FirefoxTheme.colors.layer2,
elevation = 6.dp 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. * 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 modifier [Modifier] used to draw the image content.
* @param contentScale [ContentScale] used to draw image content. * @param contentScale [ContentScale] used to draw image content.
* @param alignment [Alignment] used to draw the image content. * @param alignment [Alignment] used to draw the image content.
* is null.
*/ */
@Composable @Composable
@Suppress("LongParameterList") @Suppress("LongParameterList")
private fun RecentTabImage( private fun RecentTabImage(
tab: RecentTab.Tab, tab: RecentTab,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.FillWidth, contentScale: ContentScale = ContentScale.FillWidth,
alignment: Alignment = Alignment.TopCenter alignment: Alignment = Alignment.TopCenter
) { ) {
val previewImageUrl = tab.state.content.previewImageUrl val (previewImageUrl, loaderUrl, key) = when (tab) {
val thumbnail = tab.state.content.thumbnail 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 { when {
!previewImageUrl.isNullOrEmpty() -> { !previewImageUrl.isNullOrEmpty() -> {
@ -287,15 +418,10 @@ private fun RecentTabImage(
modifier = modifier, modifier = modifier,
backgroundColor = colorResource(id = R.color.photonGrey20) backgroundColor = colorResource(id = R.color.photonGrey20)
) { ) {
components.core.icons.Loader(tab.state.content.url) { components.core.icons.Loader(loaderUrl) {
Placeholder { Placeholder {
Box( Box(
modifier = Modifier.background( modifier = Modifier.background(color = FirefoxTheme.colors.layer3)
color = when (isSystemInDarkTheme()) {
true -> PhotonColors.DarkGrey30
false -> PhotonColors.LightGrey30
}
)
) )
} }
@ -317,7 +443,7 @@ private fun RecentTabImage(
} }
ThumbnailImage( ThumbnailImage(
tabId = tab.state.id, key = key,
modifier = modifier, modifier = modifier,
contentScale = contentScale, contentScale = contentScale,
alignment = alignment 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]. * 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. * A recent tab subtitle.
* *
@ -465,18 +618,18 @@ private fun RecentTabSubtitle(subtitle: String) {
@Composable @Composable
private fun ThumbnailImage( private fun ThumbnailImage(
tabId: String, key: String,
modifier: Modifier, modifier: Modifier,
contentScale: ContentScale, contentScale: ContentScale,
alignment: Alignment 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 size = LocalDensity.current.run { 108.dp.toPx().toInt() }
val request = ImageLoadRequest(tabId, size) val request = ImageLoadRequest(key, size)
val storage = components.core.thumbnailStorage val storage = components.core.thumbnailStorage
val bitmap = rememberBitmap.value val bitmap = rememberBitmap.value
LaunchedEffect(tabId) { LaunchedEffect(key) {
rememberBitmap.value = storage.loadThumbnail(request).await()?.asImageBitmap() 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)
)
}

View File

@ -127,6 +127,10 @@
<!-- Text for the number of tabs in a group in the 'Jump back in' section of the new tab <!-- 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. --> %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> <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 <!-- Text for the menu button to remove a grouped highlight from the user's browsing history
in the Recently visited section --> in the Recently visited section -->
<string name="recent_tab_menu_item_remove">Remove</string> <string name="recent_tab_menu_item_remove">Remove</string>