For #23238 - Refactor synced tabs sections to be collapsible
This commit is contained in:
parent
e3037ad767
commit
ae9f3ab074
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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. -->
|
||||||
|
|
Loading…
Reference in New Issue
Block a user