Create HorizontalFloatingToolbar wrapper in our components.
This commit is contained in:
parent
ff257164d3
commit
86c7d04176
3 changed files with 252 additions and 35 deletions
|
|
@ -13,7 +13,6 @@ package io.element.android.features.home.impl
|
|||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
|
|
@ -25,14 +24,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
|
||||
import androidx.compose.material3.HorizontalFloatingToolbar
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
|
|
@ -65,6 +61,9 @@ import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalFloatingToolbar
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalFloatingToolbarItem
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalFloatingToolbarSeparator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
|
|
@ -189,12 +188,10 @@ private fun HomeScaffold(
|
|||
onAccountSwitch = {
|
||||
state.eventSink(HomeEvent.SwitchToAccount(it))
|
||||
},
|
||||
onCreateSpace = onCreateSpaceClick,
|
||||
scrollBehavior = scrollBehavior,
|
||||
displayFilters = state.displayRoomListFilters,
|
||||
filtersState = roomListState.filtersState,
|
||||
spaceFiltersState = roomListState.spaceFiltersState,
|
||||
canCreateSpaces = state.homeSpacesState.canCreateSpaces,
|
||||
canReportBug = state.canReportBug,
|
||||
modifier = Modifier.hazeEffect(
|
||||
state = hazeState,
|
||||
|
|
@ -226,12 +223,21 @@ private fun HomeScaffold(
|
|||
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(item))
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> HomeFloatingActionButton(onStartChatClick, CommonStrings.action_create_a_room)
|
||||
HomeNavigationBarItem.Spaces -> HomeFloatingActionButton(onCreateSpaceClick, CommonStrings.action_create_space)
|
||||
floatingActionButton = when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> {
|
||||
{
|
||||
HomeFloatingActionButton(onStartChatClick, CommonStrings.action_create_room)
|
||||
}
|
||||
}
|
||||
}
|
||||
HomeNavigationBarItem.Spaces -> if (state.homeSpacesState.canCreateSpaces) {
|
||||
{
|
||||
HomeFloatingActionButton(onCreateSpaceClick, CommonStrings.action_create_space)
|
||||
}
|
||||
} else {
|
||||
// No FAB for spaces if we cannot create spaces
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -311,33 +317,31 @@ private fun HomeFloatingActionButton(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun HomeBottomBar(
|
||||
currentHomeNavigationBarItem: HomeNavigationBarItem,
|
||||
onItemClick: (HomeNavigationBarItem) -> Unit,
|
||||
floatingActionButton: @Composable () -> Unit,
|
||||
floatingActionButton: (@Composable () -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HorizontalFloatingToolbar(
|
||||
expanded = true,
|
||||
floatingActionButton = floatingActionButton,
|
||||
modifier = modifier
|
||||
.padding(bottom = ScreenOffset)
|
||||
.zIndex(1f),
|
||||
) {
|
||||
HomeNavigationBarItem.entries.forEach { item ->
|
||||
val isSelected = currentHomeNavigationBarItem == item
|
||||
IconButton(
|
||||
onClick = { onItemClick(item) },
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
imageVector = item.icon(isSelected),
|
||||
contentDescription = stringResource(item.labelRes),
|
||||
)
|
||||
}
|
||||
HomeNavigationBarItem.entries.forEachIndexed { index, item ->
|
||||
if (index > 0) {
|
||||
HorizontalFloatingToolbarSeparator()
|
||||
}
|
||||
val isSelected = currentHomeNavigationBarItem == item
|
||||
HorizontalFloatingToolbarItem(
|
||||
icon = item.icon(isSelected),
|
||||
tooltipLabel = stringResource(item.labelRes),
|
||||
isSelected = isSelected,
|
||||
onClick = { onItemClick(item) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,9 +87,7 @@ fun HomeTopBar(
|
|||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
onAccountSwitch: (SessionId) -> Unit,
|
||||
onCreateSpace: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
canCreateSpaces: Boolean,
|
||||
canReportBug: Boolean,
|
||||
displayFilters: Boolean,
|
||||
filtersState: RoomListFiltersState,
|
||||
|
|
@ -346,8 +344,6 @@ internal fun HomeTopBarPreview() = ElementPreview {
|
|||
onOpenSettings = {},
|
||||
onAccountSwitch = {},
|
||||
onToggleSearch = {},
|
||||
onCreateSpace = {},
|
||||
canCreateSpaces = true,
|
||||
canReportBug = true,
|
||||
displayFilters = true,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
|
|
@ -392,8 +388,6 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
|||
onOpenSettings = {},
|
||||
onAccountSwitch = {},
|
||||
onToggleSearch = {},
|
||||
onCreateSpace = {},
|
||||
canCreateSpaces = true,
|
||||
canReportBug = true,
|
||||
displayFilters = false,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
|
|
@ -415,8 +409,6 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
|||
onOpenSettings = {},
|
||||
onAccountSwitch = {},
|
||||
onToggleSearch = {},
|
||||
onCreateSpace = {},
|
||||
canCreateSpaces = true,
|
||||
canReportBug = true,
|
||||
displayFilters = true,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
|
|
@ -438,8 +430,6 @@ internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
|||
onOpenSettings = {},
|
||||
onAccountSwitch = {},
|
||||
onToggleSearch = {},
|
||||
onCreateSpace = {},
|
||||
canCreateSpaces = true,
|
||||
canReportBug = true,
|
||||
displayFilters = true,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FloatingToolbarColors
|
||||
import androidx.compose.material3.FloatingToolbarDefaults
|
||||
import androidx.compose.material3.FloatingToolbarHorizontalFabPosition
|
||||
import androidx.compose.material3.FloatingToolbarScrollBehavior
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
|
||||
import io.element.android.libraries.designsystem.components.tooltip.PlainTooltip
|
||||
import io.element.android.libraries.designsystem.components.tooltip.TooltipBox
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=4457-1136
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HorizontalFloatingToolbar(
|
||||
modifier: Modifier = Modifier,
|
||||
expanded: Boolean = true,
|
||||
floatingActionButton: (@Composable () -> Unit)? = null,
|
||||
colors: FloatingToolbarColors = FloatingToolbarDefaults.standardFloatingToolbarColors().copy(
|
||||
toolbarContainerColor = ElementTheme.colors.bgSubtleSecondary,
|
||||
),
|
||||
contentPadding: PaddingValues = PaddingValues(
|
||||
vertical = 8.dp,
|
||||
horizontal = 12.dp,
|
||||
),
|
||||
scrollBehavior: FloatingToolbarScrollBehavior? = null,
|
||||
shape: Shape = FloatingToolbarDefaults.ContainerShape,
|
||||
leadingContent: @Composable (RowScope.() -> Unit)? = null,
|
||||
trailingContent: @Composable (RowScope.() -> Unit)? = null,
|
||||
floatingActionButtonPosition: FloatingToolbarHorizontalFabPosition =
|
||||
FloatingToolbarHorizontalFabPosition.End,
|
||||
animationSpec: FiniteAnimationSpec<Float> = FloatingToolbarDefaults.animationSpec(),
|
||||
expandedShadowElevation: Dp = 8.dp,
|
||||
collapsedShadowElevation: Dp = if (floatingActionButton == null) {
|
||||
FloatingToolbarDefaults.ContainerCollapsedElevation
|
||||
} else {
|
||||
FloatingToolbarDefaults.ContainerCollapsedElevationWithFab
|
||||
},
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
if (floatingActionButton == null) {
|
||||
androidx.compose.material3.HorizontalFloatingToolbar(
|
||||
expanded = expanded,
|
||||
modifier = modifier,
|
||||
colors = colors,
|
||||
contentPadding = contentPadding,
|
||||
scrollBehavior = scrollBehavior,
|
||||
shape = shape,
|
||||
leadingContent = leadingContent,
|
||||
trailingContent = trailingContent,
|
||||
expandedShadowElevation = expandedShadowElevation,
|
||||
collapsedShadowElevation = collapsedShadowElevation,
|
||||
content = content,
|
||||
)
|
||||
} else {
|
||||
androidx.compose.material3.HorizontalFloatingToolbar(
|
||||
expanded = expanded,
|
||||
floatingActionButton = floatingActionButton,
|
||||
modifier = modifier,
|
||||
colors = colors,
|
||||
contentPadding = contentPadding,
|
||||
scrollBehavior = scrollBehavior,
|
||||
shape = shape,
|
||||
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||
animationSpec = animationSpec,
|
||||
expandedShadowElevation = expandedShadowElevation,
|
||||
collapsedShadowElevation = collapsedShadowElevation,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HorizontalFloatingToolbarItem(
|
||||
icon: ImageVector,
|
||||
tooltipLabel: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
counter: Int? = null,
|
||||
forceRenderingTooltip: Boolean = false,
|
||||
) {
|
||||
TooltipBox(
|
||||
positionProvider =
|
||||
TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above
|
||||
),
|
||||
tooltip = { PlainTooltip { Text(tooltipLabel) } },
|
||||
state = rememberTooltipState(
|
||||
initialIsVisible = forceRenderingTooltip,
|
||||
),
|
||||
modifier = modifier,
|
||||
) {
|
||||
val colors = if (isSelected) {
|
||||
IconButtonDefaults.filledIconButtonColors().copy(
|
||||
containerColor = ElementTheme.colors.bgCanvasDefault,
|
||||
contentColor = ElementTheme.colors.iconPrimary,
|
||||
)
|
||||
} else {
|
||||
IconButtonDefaults.filledIconButtonColors().copy(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
}
|
||||
Box {
|
||||
FilledIconButton(
|
||||
modifier = Modifier.widthIn(min = 56.dp),
|
||||
colors = colors,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
imageVector = icon,
|
||||
contentDescription = tooltipLabel,
|
||||
)
|
||||
}
|
||||
if (counter != null) {
|
||||
CounterAtom(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 6.dp, end = 3.dp),
|
||||
count = counter,
|
||||
textStyle = ElementTheme.typography.fontBodyXsMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizontalFloatingToolbarSeparator(modifier: Modifier = Modifier) {
|
||||
Spacer(modifier = modifier.width(16.dp))
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HorizontalFloatingToolbarPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Plus(),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HorizontalFloatingToolbarNoFabPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
floatingActionButton = null,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ContentToPreview(
|
||||
floatingActionButton: (@Composable () -> Unit)?,
|
||||
) {
|
||||
HorizontalFloatingToolbar(
|
||||
modifier = Modifier.padding(28.dp),
|
||||
floatingActionButton = floatingActionButton,
|
||||
) {
|
||||
listOf(
|
||||
CompoundIcons.ChatSolid(),
|
||||
CompoundIcons.Space(),
|
||||
).forEachIndexed { index, icon ->
|
||||
if (index > 0) {
|
||||
HorizontalFloatingToolbarSeparator()
|
||||
}
|
||||
HorizontalFloatingToolbarItem(
|
||||
icon = icon,
|
||||
tooltipLabel = "Label",
|
||||
isSelected = index == 0,
|
||||
counter = if (index == 0) 6 else null,
|
||||
forceRenderingTooltip = true,
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue