Bug 1822192 - Add empty state UI for Normal and Private tab pages

fenix/114.1.0
Noah Bond 1 year ago committed by mergify[bot]
parent 36a7693e79
commit 8973d3ef60

@ -8,6 +8,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -17,21 +18,25 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.storage.sync.TabEntry
import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.compose.Divider
@ -93,20 +98,12 @@ fun TabsTray(
onInactiveTabClose: (TabSessionState) -> Unit,
onSyncedTabClick: (SyncTab) -> Unit,
) {
val selectedTabId = browserStore
.observeAsComposableState { state -> state.selectedTabId }.value
val normalTabCount = browserStore
.observeAsComposableState { state -> state.normalTabs.size }.value ?: 0
val multiselectMode = tabsTrayStore
.observeAsComposableState { state -> state.mode }.value ?: TabsTrayState.Mode.Normal
val selectedPage = tabsTrayStore
.observeAsComposableState { state -> state.selectedPage }.value ?: Page.NormalTabs
val normalTabs = tabsTrayStore
.observeAsComposableState { state -> state.normalTabs }.value ?: emptyList()
val privateTabs = tabsTrayStore
.observeAsComposableState { state -> state.privateTabs }.value ?: emptyList()
val inactiveTabsExpanded = appStore
.observeAsComposableState { state -> state.inactiveTabsExpanded }.value ?: false
val inactiveTabs = tabsTrayStore
.observeAsComposableState { state -> state.inactiveTabs }.value ?: emptyList()
val pagerState = rememberPagerState(initialPage = selectedPage.ordinal)
val isInMultiSelectMode = multiselectMode is TabsTrayState.Mode.Select
@ -138,7 +135,7 @@ fun TabsTray(
TabsTrayBanner(
selectMode = multiselectMode,
selectedPage = selectedPage,
normalTabCount = normalTabs.size + inactiveTabs.size,
normalTabCount = normalTabCount,
isInDebugMode = isInDebugMode,
onTabPageIndicatorClicked = onTabPageClick,
)
@ -155,85 +152,43 @@ fun TabsTray(
) { position ->
when (Page.positionToPage(position)) {
Page.NormalTabs -> {
if (normalTabs.isNotEmpty() || inactiveTabs.isNotEmpty()) {
val showInactiveTabsAutoCloseDialog =
shouldShowInactiveTabsAutoCloseDialog(inactiveTabs.size)
var showAutoCloseDialog by remember { mutableStateOf(showInactiveTabsAutoCloseDialog) }
val optionalInactiveTabsHeader: (@Composable () -> Unit)? = if (inactiveTabs.isEmpty()) {
null
} else {
{
InactiveTabsList(
inactiveTabs = inactiveTabs,
expanded = inactiveTabsExpanded,
showAutoCloseDialog = showAutoCloseDialog,
onHeaderClick = onInactiveTabsHeaderClick,
onDeleteAllButtonClick = onDeleteAllInactiveTabsClick,
onAutoCloseDismissClick = {
onInactiveTabAutoCloseDialogCloseButtonClick()
showAutoCloseDialog = !showAutoCloseDialog
},
onEnableAutoCloseClick = {
onEnableInactiveTabAutoCloseClick()
showAutoCloseDialog = !showAutoCloseDialog
},
onTabClick = onInactiveTabClick,
onTabCloseClick = onInactiveTabClose,
)
}
}
if (showInactiveTabsAutoCloseDialog) {
onInactiveTabsAutoCloseDialogShown()
}
TabLayout(
tabs = normalTabs,
displayTabsInGrid = displayTabsInGrid,
selectedTabId = selectedTabId,
selectionMode = multiselectMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = handleTabClick,
onTabLongClick = onTabLongClick,
header = optionalInactiveTabsHeader,
)
} else {
Text(
text = "Empty state",
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.body1,
)
}
NormalTabsPage(
appStore = appStore,
browserStore = browserStore,
tabsTrayStore = tabsTrayStore,
displayTabsInGrid = displayTabsInGrid,
selectionMode = multiselectMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = handleTabClick,
onTabLongClick = onTabLongClick,
shouldShowInactiveTabsAutoCloseDialog = shouldShowInactiveTabsAutoCloseDialog,
onInactiveTabsHeaderClick = onInactiveTabsHeaderClick,
onDeleteAllInactiveTabsClick = onDeleteAllInactiveTabsClick,
onInactiveTabsAutoCloseDialogShown = onInactiveTabsAutoCloseDialogShown,
onInactiveTabAutoCloseDialogCloseButtonClick = onInactiveTabAutoCloseDialogCloseButtonClick,
onEnableInactiveTabAutoCloseClick = onEnableInactiveTabAutoCloseClick,
onInactiveTabClick = onInactiveTabClick,
onInactiveTabClose = onInactiveTabClose,
)
}
Page.PrivateTabs -> {
if (privateTabs.isNotEmpty()) {
TabLayout(
tabs = privateTabs,
displayTabsInGrid = displayTabsInGrid,
selectedTabId = selectedTabId,
selectionMode = multiselectMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = handleTabClick,
onTabLongClick = onTabLongClick,
)
} else {
Text(
text = "Empty state",
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.body1,
)
}
PrivateTabsPage(
browserStore = browserStore,
tabsTrayStore = tabsTrayStore,
displayTabsInGrid = displayTabsInGrid,
selectionMode = multiselectMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = handleTabClick,
onTabLongClick = onTabLongClick,
)
}
Page.SyncedTabs -> {
val syncedTabs = tabsTrayStore
.observeAsComposableState { state -> state.syncedTabs }.value ?: emptyList()
SyncedTabsList(
syncedTabs = syncedTabs,
taskContinuityEnabled = true,
Page.SyncedTabs -> {
SyncedTabsPage(
tabsTrayStore = tabsTrayStore,
onTabClick = onSyncedTabClick,
)
}
@ -243,6 +198,153 @@ fun TabsTray(
}
}
@Composable
@Suppress("LongParameterList")
private fun NormalTabsPage(
appStore: AppStore,
browserStore: BrowserStore,
tabsTrayStore: TabsTrayStore,
displayTabsInGrid: Boolean,
selectionMode: TabsTrayState.Mode,
onTabClose: (TabSessionState) -> Unit,
onTabMediaClick: (TabSessionState) -> Unit,
onTabClick: (TabSessionState) -> Unit,
onTabLongClick: (TabSessionState) -> Unit,
shouldShowInactiveTabsAutoCloseDialog: (Int) -> Boolean,
onInactiveTabsHeaderClick: (Boolean) -> Unit,
onDeleteAllInactiveTabsClick: () -> Unit,
onInactiveTabsAutoCloseDialogShown: () -> Unit,
onInactiveTabAutoCloseDialogCloseButtonClick: () -> Unit,
onEnableInactiveTabAutoCloseClick: () -> Unit,
onInactiveTabClick: (TabSessionState) -> Unit,
onInactiveTabClose: (TabSessionState) -> Unit,
) {
val inactiveTabsExpanded = appStore
.observeAsComposableState { state -> state.inactiveTabsExpanded }.value ?: false
val selectedTabId = browserStore
.observeAsComposableState { state -> state.selectedTabId }.value
val normalTabs = tabsTrayStore
.observeAsComposableState { state -> state.normalTabs }.value ?: emptyList()
val inactiveTabs = tabsTrayStore
.observeAsComposableState { state -> state.inactiveTabs }.value ?: emptyList()
if (normalTabs.isNotEmpty() || inactiveTabs.isNotEmpty()) {
val showInactiveTabsAutoCloseDialog =
shouldShowInactiveTabsAutoCloseDialog(inactiveTabs.size)
var showAutoCloseDialog by remember { mutableStateOf(showInactiveTabsAutoCloseDialog) }
val optionalInactiveTabsHeader: (@Composable () -> Unit)? = if (inactiveTabs.isEmpty()) {
null
} else {
{
InactiveTabsList(
inactiveTabs = inactiveTabs,
expanded = inactiveTabsExpanded,
showAutoCloseDialog = showAutoCloseDialog,
onHeaderClick = onInactiveTabsHeaderClick,
onDeleteAllButtonClick = onDeleteAllInactiveTabsClick,
onAutoCloseDismissClick = {
onInactiveTabAutoCloseDialogCloseButtonClick()
showAutoCloseDialog = !showAutoCloseDialog
},
onEnableAutoCloseClick = {
onEnableInactiveTabAutoCloseClick()
showAutoCloseDialog = !showAutoCloseDialog
},
onTabClick = onInactiveTabClick,
onTabCloseClick = onInactiveTabClose,
)
}
}
if (showInactiveTabsAutoCloseDialog) {
onInactiveTabsAutoCloseDialogShown()
}
TabLayout(
tabs = normalTabs,
displayTabsInGrid = displayTabsInGrid,
selectedTabId = selectedTabId,
selectionMode = selectionMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = onTabClick,
onTabLongClick = onTabLongClick,
header = optionalInactiveTabsHeader,
)
} else {
EmptyTabPage(isPrivate = false)
}
}
@Composable
@Suppress("LongParameterList")
private fun PrivateTabsPage(
browserStore: BrowserStore,
tabsTrayStore: TabsTrayStore,
displayTabsInGrid: Boolean,
selectionMode: TabsTrayState.Mode,
onTabClose: (TabSessionState) -> Unit,
onTabMediaClick: (TabSessionState) -> Unit,
onTabClick: (TabSessionState) -> Unit,
onTabLongClick: (TabSessionState) -> Unit,
) {
val selectedTabId = browserStore
.observeAsComposableState { state -> state.selectedTabId }.value
val privateTabs = tabsTrayStore
.observeAsComposableState { state -> state.privateTabs }.value ?: emptyList()
if (privateTabs.isNotEmpty()) {
TabLayout(
tabs = privateTabs,
displayTabsInGrid = displayTabsInGrid,
selectedTabId = selectedTabId,
selectionMode = selectionMode,
onTabClose = onTabClose,
onTabMediaClick = onTabMediaClick,
onTabClick = onTabClick,
onTabLongClick = onTabLongClick,
)
} else {
EmptyTabPage(isPrivate = true)
}
}
@Composable
private fun SyncedTabsPage(
tabsTrayStore: TabsTrayStore,
onTabClick: (SyncTab) -> Unit,
) {
val syncedTabs = tabsTrayStore
.observeAsComposableState { state -> state.syncedTabs }.value ?: emptyList()
SyncedTabsList(
syncedTabs = syncedTabs,
taskContinuityEnabled = true,
onTabClick = onTabClick,
)
}
@Composable
private fun EmptyTabPage(isPrivate: Boolean) {
Box(modifier = Modifier.fillMaxSize()) {
Text(
text = stringResource(
id = if (isPrivate) {
R.string.no_private_tabs_description
} else {
R.string.no_open_tabs_description
},
),
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 80.dp),
color = FirefoxTheme.colors.textSecondary,
style = FirefoxTheme.typography.body1,
)
}
}
@LightDarkPreview
@Composable
private fun TabsTrayPreview() {

Loading…
Cancel
Save