For #23238 - Refactor synced tabs sections to be collapsible

This commit is contained in:
Noah Bond 2022-02-22 11:59:46 -08:00 committed by mergify[bot]
parent e3037ad767
commit ae9f3ab074
2 changed files with 81 additions and 29 deletions

View File

@ -16,6 +16,7 @@ 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.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -33,6 +34,8 @@ import androidx.compose.material.Divider
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -54,10 +57,13 @@ import org.mozilla.fenix.compose.SecondaryText
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import mozilla.components.browser.storage.sync.Tab as SyncTab import mozilla.components.browser.storage.sync.Tab as SyncTab
private const val EXPANDED_BY_DEFAULT = true
/** /**
* Top-level list UI for displaying Synced Tabs in the Tabs Tray. * Top-level list UI for displaying Synced Tabs in the Tabs Tray.
* *
* @param syncedTabs The tab UI items to be displayed. * @param syncedTabs The tab UI items to be displayed.
* @param taskContinuityEnabled Indicates whether the Task Continuity enhancements should be visible for users.
* @param onTabClick The lambda for handling clicks on synced tabs. * @param onTabClick The lambda for handling clicks on synced tabs.
*/ */
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@ -68,30 +74,40 @@ fun SyncedTabsList(
onTabClick: (SyncTab) -> Unit, onTabClick: (SyncTab) -> Unit,
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val expandedState = remember(syncedTabs) { syncedTabs.map { EXPANDED_BY_DEFAULT }.toMutableStateList() }
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = listState, state = listState,
) { ) {
if (taskContinuityEnabled) { if (taskContinuityEnabled) {
syncedTabs.forEach { syncedTabItem -> syncedTabs.forEachIndexed { index, syncedTabItem ->
when (syncedTabItem) { when (syncedTabItem) {
is SyncedTabsListItem.DeviceSection -> { is SyncedTabsListItem.DeviceSection -> {
val sectionExpanded = expandedState[index]
stickyHeader { stickyHeader {
SyncedTabsDeviceItem(deviceName = syncedTabItem.displayName) SyncedTabsSectionHeader(
sectionText = syncedTabItem.displayName,
expanded = sectionExpanded,
) {
expandedState[index] = !sectionExpanded
}
} }
if (syncedTabItem.tabs.isNotEmpty()) { if (sectionExpanded) {
items(syncedTabItem.tabs) { syncedTab -> if (syncedTabItem.tabs.isNotEmpty()) {
SyncedTabsTabItem( items(syncedTabItem.tabs) { syncedTab ->
tabTitleText = syncedTab.displayTitle, SyncedTabsTabItem(
url = syncedTab.displayURL, tabTitleText = syncedTab.displayTitle,
) { url = syncedTab.displayURL,
onTabClick(syncedTab.tab) ) {
onTabClick(syncedTab.tab)
}
} }
} else {
item { SyncedTabsNoTabsItem() }
} }
} else {
item { SyncedTabsNoTabsItem() }
} }
} }
@ -111,7 +127,7 @@ fun SyncedTabsList(
} else { } else {
items(syncedTabs) { syncedTabItem -> items(syncedTabs) { syncedTabItem ->
when (syncedTabItem) { when (syncedTabItem) {
is SyncedTabsListItem.Device -> SyncedTabsDeviceItem(deviceName = syncedTabItem.displayName) is SyncedTabsListItem.Device -> SyncedTabsSectionHeader(sectionText = syncedTabItem.displayName)
is SyncedTabsListItem.Error -> SyncedTabsErrorItem( is SyncedTabsListItem.Error -> SyncedTabsErrorItem(
errorText = syncedTabItem.errorText, errorText = syncedTabItem.errorText,
errorButton = syncedTabItem.errorButton errorButton = syncedTabItem.errorButton
@ -135,32 +151,57 @@ fun SyncedTabsList(
item { item {
// The Spacer here is to act as a footer to add padding to the bottom of the list so // The Spacer here is to act as a footer to add padding to the bottom of the list so
// the FAB or any potential SnackBar doesn't overlap with the items at the end. // the FAB or any potential SnackBar doesn't overlap with the items at the end.
Spacer(Modifier.height(240.dp)) Spacer(modifier = Modifier.height(240.dp))
} }
} }
} }
/** /**
* Text header for sections of synced tabs * Collapsible header for sections of synced tabs
* *
* @param deviceName The name of the user's device connected that has synced tabs. * @param sectionText The section title for a group of synced tabs.
* @param expanded Indicates whether the section of content is expanded. If null, the Icon will be hidden.
* @param onClick Optional lambda for handling section header clicks.
*/ */
@Composable @Composable
fun SyncedTabsDeviceItem(deviceName: String) { fun SyncedTabsSectionHeader(
sectionText: String,
expanded: Boolean? = null,
onClick: () -> Unit = {},
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(FirefoxTheme.colors.layer1) .background(FirefoxTheme.colors.layer1)
.clickable(onClick = onClick)
) { ) {
PrimaryText( Row(
text = deviceName,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, top = 16.dp, end = 8.dp, bottom = 8.dp), .padding(top = 16.dp, bottom = 8.dp, start = 16.dp, end = 16.dp),
fontSize = 14.sp, verticalAlignment = Alignment.CenterVertically,
fontFamily = FontFamily(Font(R.font.metropolis_semibold)), ) {
maxLines = 1 PrimaryText(
) text = sectionText,
fontSize = 14.sp,
fontFamily = FontFamily(Font(R.font.metropolis_semibold)),
maxLines = 1,
)
expanded?.let {
Spacer(modifier = Modifier.width(8.dp))
Icon(
painter = painterResource(
if (expanded) R.drawable.ic_chevron_down else R.drawable.ic_chevron_up
),
contentDescription = stringResource(
if (expanded) R.string.synced_tabs_collapse_group else R.string.synced_tabs_expand_group,
),
tint = FirefoxTheme.colors.textPrimary,
)
}
}
Divider(color = FirefoxTheme.colors.borderPrimary) Divider(color = FirefoxTheme.colors.borderPrimary)
} }
@ -272,7 +313,7 @@ fun SyncedTabsErrorButton(buttonText: String, onClick: () -> Unit) {
tint = FirefoxTheme.colors.textOnColorPrimary, tint = FirefoxTheme.colors.textOnColorPrimary,
) )
Spacer(Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = buttonText, text = buttonText,
@ -305,21 +346,28 @@ fun SyncedTabsNoTabsItem() {
private fun SyncedTabsListItemsPreview() { private fun SyncedTabsListItemsPreview() {
FirefoxTheme { FirefoxTheme {
Column(Modifier.background(FirefoxTheme.colors.layer1)) { Column(Modifier.background(FirefoxTheme.colors.layer1)) {
SyncedTabsDeviceItem(deviceName = "Google Pixel Pro Max +Ultra 5000") SyncedTabsSectionHeader(sectionText = "Google Pixel Pro Max +Ultra 5000")
Spacer(Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
SyncedTabsSectionHeader(
sectionText = "Collapsible Google Pixel Pro Max +Ultra 5000",
expanded = true,
) { println("Clicked section header") }
Spacer(modifier = Modifier.height(16.dp))
SyncedTabsTabItem(tabTitleText = "Mozilla", url = "www.mozilla.org") { println("Clicked tab") } SyncedTabsTabItem(tabTitleText = "Mozilla", url = "www.mozilla.org") { println("Clicked tab") }
Spacer(Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
SyncedTabsErrorItem(errorText = stringResource(R.string.synced_tabs_reauth)) SyncedTabsErrorItem(errorText = stringResource(R.string.synced_tabs_reauth))
Spacer(Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
SyncedTabsNoTabsItem() SyncedTabsNoTabsItem()
Spacer(Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
} }
} }

View File

@ -1647,6 +1647,10 @@
<string name="synced_tabs_sign_in_button">Sign in to sync</string> <string name="synced_tabs_sign_in_button">Sign in to sync</string>
<!-- The text displayed when a synced device has no tabs to show in the list of Synced Tabs. --> <!-- The text displayed when a synced device has no tabs to show in the list of Synced Tabs. -->
<string name="synced_tabs_no_open_tabs">No open tabs</string> <string name="synced_tabs_no_open_tabs">No open tabs</string>
<!-- Content description for expanding a group of synced tabs. -->
<string name="synced_tabs_expand_group">Expand group of synced tabs</string>
<!-- Content description for collapsing a group of synced tabs. -->
<string name="synced_tabs_collapse_group">Collapse group of synced tabs</string>
<!-- Top Sites --> <!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. --> <!-- Title text displayed in the dialog when top sites limit is reached. -->