Bug 1814994 - Create Tabs Tray list and grid layouts

fenix/113.0
Noah Bond 1 year ago committed by mergify[bot]
parent fd61f88da2
commit 0acebea167

@ -12,29 +12,39 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.compose.Divider
import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
/** /**
* Top-level UI for displaying the Tabs Tray feature. * Top-level UI for displaying the Tabs Tray feature.
* *
* @param tabsTrayStore [TabsTrayStore] used to listen for changes to [TabsTrayState]. * @param tabsTrayStore [TabsTrayStore] used to listen for changes to [TabsTrayState].
* @param displayTabsInGrid Whether the normal and private tabs should be displayed in a grid.
*/ */
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class, ExperimentalComposeUiApi::class)
@Composable @Composable
fun TabsTray( fun TabsTray(
tabsTrayStore: TabsTrayStore, tabsTrayStore: TabsTrayStore,
displayTabsInGrid: Boolean,
) { ) {
val multiselectMode = tabsTrayStore val multiselectMode = tabsTrayStore
.observeAsComposableState { state -> state.mode }.value ?: TabsTrayState.Mode.Normal .observeAsComposableState { state -> state.mode }.value ?: TabsTrayState.Mode.Normal
val normalTabs = tabsTrayStore
.observeAsComposableState { state -> state.normalTabs }.value ?: emptyList()
val pagerState = rememberPagerState(initialPage = 0) val pagerState = rememberPagerState(initialPage = 0)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val animateScrollToPage: ((Page) -> Unit) = { page -> val animateScrollToPage: ((Page) -> Unit) = { page ->
@ -48,10 +58,12 @@ fun TabsTray(
.fillMaxSize() .fillMaxSize()
.background(FirefoxTheme.colors.layer1), .background(FirefoxTheme.colors.layer1),
) { ) {
TabsTrayBanner( Box(modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
isInMultiSelectMode = multiselectMode is TabsTrayState.Mode.Select, TabsTrayBanner(
onTabPageIndicatorClicked = animateScrollToPage, isInMultiSelectMode = multiselectMode is TabsTrayState.Mode.Select,
) onTabPageIndicatorClicked = animateScrollToPage,
)
}
Divider() Divider()
@ -64,12 +76,17 @@ fun TabsTray(
) { position -> ) { position ->
when (Page.positionToPage(position)) { when (Page.positionToPage(position)) {
Page.NormalTabs -> { Page.NormalTabs -> {
Text( FirefoxTheme(theme = Theme.getTheme(allowPrivateTheme = false)) {
text = "Normal tabs", if (displayTabsInGrid) {
modifier = Modifier.padding(all = 16.dp), TabGrid(
color = FirefoxTheme.colors.textPrimary, tabs = normalTabs,
style = FirefoxTheme.typography.body1, )
) } else {
TabList(
tabs = normalTabs,
)
}
}
} }
Page.PrivateTabs -> { Page.PrivateTabs -> {
Text( Text(
@ -96,9 +113,16 @@ fun TabsTray(
@LightDarkPreview @LightDarkPreview
@Composable @Composable
private fun TabsTrayPreview() { private fun TabsTrayPreview() {
val store = TabsTrayStore(
initialState = TabsTrayState(
normalTabs = generateFakeTabsList(),
),
)
FirefoxTheme { FirefoxTheme {
TabsTray( TabsTray(
tabsTrayStore = TabsTrayStore(), tabsTrayStore = store,
displayTabsInGrid = false,
) )
} }
} }
@ -109,12 +133,25 @@ private fun TabsTrayMultiSelectPreview() {
val store = TabsTrayStore( val store = TabsTrayStore(
initialState = TabsTrayState( initialState = TabsTrayState(
mode = TabsTrayState.Mode.Select(setOf()), mode = TabsTrayState.Mode.Select(setOf()),
normalTabs = generateFakeTabsList(),
), ),
) )
FirefoxTheme { FirefoxTheme {
TabsTray( TabsTray(
tabsTrayStore = store, tabsTrayStore = store,
displayTabsInGrid = true,
) )
} }
} }
private fun generateFakeTabsList(tabCount: Int = 10): List<TabSessionState> {
val fakeTab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
return List(tabCount) { fakeTab }
}

@ -208,6 +208,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
FirefoxTheme { FirefoxTheme {
TabsTray( TabsTray(
tabsTrayStore = tabsTrayStore, tabsTrayStore = tabsTrayStore,
displayTabsInGrid = requireContext().settings().gridTabView,
) )
} }
} }

@ -0,0 +1,145 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
package org.mozilla.fenix.tabstray
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.tabstray.TabGridItem
import org.mozilla.fenix.compose.tabstray.TabListItem
import org.mozilla.fenix.tabstray.ext.MIN_COLUMN_WIDTH_DP
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Top-level UI for displaying a list of tabs.
*
* @param tabs The list of [TabSessionState] to display.
*/
@Composable
fun TabList(
tabs: List<TabSessionState>,
) {
val tabListBottomPadding = dimensionResource(id = R.dimen.tab_tray_list_bottom_padding)
val state = rememberLazyListState()
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = state,
) {
items(
items = tabs,
key = { tab -> tab.id },
) { tab ->
TabListItem(
tab = tab,
onCloseClick = {},
onMediaClick = {},
onClick = {},
onLongClick = {},
)
}
item {
Spacer(modifier = Modifier.height(tabListBottomPadding))
}
}
}
/**
* Top-level UI for displaying a grid of tabs.
*
* @param tabs The list of [TabSessionState] to display.
*/
@Composable
fun TabGrid(
tabs: List<TabSessionState>,
) {
val tabListBottomPadding = dimensionResource(id = R.dimen.tab_tray_list_bottom_padding)
val state = rememberLazyGridState()
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = MIN_COLUMN_WIDTH_DP.dp),
modifier = Modifier.fillMaxSize(),
state = state,
) {
items(
items = tabs,
key = { tab -> tab.id },
) { tab ->
TabGridItem(
tab = tab,
onCloseClick = {},
onMediaClick = {},
onClick = {},
onLongClick = {},
)
}
item(span = { GridItemSpan(maxLineSpan) }) {
Spacer(modifier = Modifier.height(tabListBottomPadding))
}
}
}
@LightDarkPreview
@Composable
private fun TabListPreview() {
FirefoxTheme {
Box(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer1),
) {
TabList(
tabs = generateFakeTabsList(),
)
}
}
}
@LightDarkPreview
@Composable
private fun TabGridPreview() {
FirefoxTheme {
Box(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer1),
) {
TabGrid(
tabs = generateFakeTabsList(),
)
}
}
}
private fun generateFakeTabsList(tabCount: Int = 10): List<TabSessionState> {
val fakeTab = TabSessionState(
id = "tabId",
content = ContentState(
url = "www.mozilla.com",
),
)
return List(tabCount) { fakeTab }
}
Loading…
Cancel
Save