Add space filter button to HomeTopBar and integrate SpaceFiltersView

This commit is contained in:
ganfra 2026-02-02 21:02:43 +01:00
parent c993a6b387
commit eb94015996
5 changed files with 112 additions and 18 deletions

View file

@ -50,6 +50,7 @@ import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
import io.element.android.features.home.impl.roomlist.RoomListEvent import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.RoomListState import io.element.android.features.home.impl.roomlist.RoomListState
import io.element.android.features.home.impl.search.RoomListSearchView import io.element.android.features.home.impl.search.RoomListSearchView
import io.element.android.features.home.impl.spacefilters.SpaceFiltersView
import io.element.android.features.home.impl.spaces.HomeSpacesView import io.element.android.features.home.impl.spaces.HomeSpacesView
import io.element.android.libraries.androidutils.throttler.FirstThrottler import io.element.android.libraries.androidutils.throttler.FirstThrottler
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
@ -168,7 +169,6 @@ private fun HomeScaffold(
topBar = { topBar = {
HomeTopBar( HomeTopBar(
selectedNavigationItem = state.currentHomeNavigationBarItem, selectedNavigationItem = state.currentHomeNavigationBarItem,
title = stringResource(state.currentHomeNavigationBarItem.labelRes),
currentUserAndNeighbors = state.currentUserAndNeighbors, currentUserAndNeighbors = state.currentUserAndNeighbors,
showAvatarIndicator = state.showAvatarIndicator, showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = roomListState.searchState.isSearchActive, areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
@ -182,6 +182,7 @@ private fun HomeScaffold(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
displayFilters = state.displayRoomListFilters, displayFilters = state.displayRoomListFilters,
filtersState = roomListState.filtersState, filtersState = roomListState.filtersState,
spaceFiltersState = roomListState.spaceFiltersState,
canCreateSpaces = state.homeSpacesState.canCreateSpaces, canCreateSpaces = state.homeSpacesState.canCreateSpaces,
canReportBug = state.canReportBug, canReportBug = state.canReportBug,
modifier = Modifier.hazeEffect( modifier = Modifier.hazeEffect(
@ -256,6 +257,7 @@ private fun HomeScaffold(
.consumeWindowInsets(padding) .consumeWindowInsets(padding)
.hazeSource(state = hazeState) .hazeSource(state = hazeState)
) )
SpaceFiltersView(roomListState.spaceFiltersState)
} }
HomeNavigationBarItem.Spaces -> { HomeNavigationBarItem.Spaces -> {
HomeSpacesView( HomeSpacesView(

View file

@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -40,10 +41,14 @@ import io.element.android.appconfig.RoomListConfig
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.HomeNavigationBarItem import io.element.android.features.home.impl.HomeNavigationBarItem
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.RoomListFiltersView import io.element.android.features.home.impl.filters.RoomListFiltersView
import io.element.android.features.home.impl.filters.aRoomListFiltersState import io.element.android.features.home.impl.filters.aRoomListFiltersState
import io.element.android.features.home.impl.spacefilters.SpaceFiltersEvent
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
import io.element.android.features.home.impl.spacefilters.aDisabledSpaceFiltersState
import io.element.android.features.home.impl.spacefilters.aSelectedSpaceFiltersState
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -75,7 +80,6 @@ import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
fun HomeTopBar( fun HomeTopBar(
selectedNavigationItem: HomeNavigationBarItem, selectedNavigationItem: HomeNavigationBarItem,
title: String,
currentUserAndNeighbors: ImmutableList<MatrixUser>, currentUserAndNeighbors: ImmutableList<MatrixUser>,
showAvatarIndicator: Boolean, showAvatarIndicator: Boolean,
areSearchResultsDisplayed: Boolean, areSearchResultsDisplayed: Boolean,
@ -89,6 +93,7 @@ fun HomeTopBar(
canReportBug: Boolean, canReportBug: Boolean,
displayFilters: Boolean, displayFilters: Boolean,
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
spaceFiltersState: SpaceFiltersState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column(modifier) { Column(modifier) {
@ -103,12 +108,21 @@ fun HomeTopBar(
scrolledContainerColor = Color.Transparent, scrolledContainerColor = Color.Transparent,
), ),
title = { title = {
val displayTitle = when (selectedNavigationItem) {
HomeNavigationBarItem.Chats -> {
when (spaceFiltersState) {
is SpaceFiltersState.Selected -> spaceFiltersState.selectedFilter.spaceRoom.displayName
else -> stringResource(selectedNavigationItem.labelRes)
}
}
HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes)
}
Text( Text(
modifier = Modifier.semantics { modifier = Modifier.semantics {
heading() heading()
}, },
style = ElementTheme.typography.aliasScreenTitle, style = ElementTheme.typography.aliasScreenTitle,
text = title, text = displayTitle,
) )
}, },
navigationIcon = { navigationIcon = {
@ -124,7 +138,8 @@ fun HomeTopBar(
HomeNavigationBarItem.Chats -> RoomListMenuItems( HomeNavigationBarItem.Chats -> RoomListMenuItems(
onToggleSearch = onToggleSearch, onToggleSearch = onToggleSearch,
onMenuActionClick = onMenuActionClick, onMenuActionClick = onMenuActionClick,
canReportBug = canReportBug canReportBug = canReportBug,
spaceFiltersState = spaceFiltersState,
) )
HomeNavigationBarItem.Spaces -> SpacesMenuItems( HomeNavigationBarItem.Spaces -> SpacesMenuItems(
canCreateSpaces = canCreateSpaces, canCreateSpaces = canCreateSpaces,
@ -154,6 +169,7 @@ private fun RoomListMenuItems(
onToggleSearch: () -> Unit, onToggleSearch: () -> Unit,
onMenuActionClick: (RoomListMenuAction) -> Unit, onMenuActionClick: (RoomListMenuAction) -> Unit,
canReportBug: Boolean, canReportBug: Boolean,
spaceFiltersState: SpaceFiltersState,
) { ) {
IconButton( IconButton(
onClick = onToggleSearch, onClick = onToggleSearch,
@ -163,6 +179,7 @@ private fun RoomListMenuItems(
contentDescription = stringResource(CommonStrings.action_search), contentDescription = stringResource(CommonStrings.action_search),
) )
} }
SpaceFilterButton(spaceFiltersState = spaceFiltersState)
if (RoomListConfig.HAS_DROP_DOWN_MENU) { if (RoomListConfig.HAS_DROP_DOWN_MENU) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
IconButton( IconButton(
@ -228,6 +245,47 @@ private fun SpacesMenuItems(
} }
} }
@Composable
private fun SpaceFilterButton(
spaceFiltersState: SpaceFiltersState,
) {
when (spaceFiltersState) {
SpaceFiltersState.Disabled -> Unit
is SpaceFiltersState.Unselected -> {
IconButton(
onClick = { spaceFiltersState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters) }
) {
Icon(
imageVector = CompoundIcons.Filter(),
contentDescription = null,
)
}
}
is SpaceFiltersState.Selecting -> {
IconButton(onClick = {}) {
Icon(
imageVector = CompoundIcons.Filter(),
contentDescription = null,
)
}
}
is SpaceFiltersState.Selected -> {
IconButton(
colors = IconButtonDefaults.iconButtonColors(
containerColor = ElementTheme.colors.bgAccentRest,
contentColor = ElementTheme.colors.iconOnSolidPrimary,
),
onClick = { spaceFiltersState.eventSink(SpaceFiltersEvent.Selected.ClearSelection) },
) {
Icon(
imageVector = CompoundIcons.Filter(),
contentDescription = null,
)
}
}
}
}
@Composable @Composable
private fun NavigationIcon( private fun NavigationIcon(
currentUserAndNeighbors: ImmutableList<MatrixUser>, currentUserAndNeighbors: ImmutableList<MatrixUser>,
@ -309,7 +367,6 @@ private fun AccountIcon(
internal fun HomeTopBarPreview() = ElementPreview { internal fun HomeTopBarPreview() = ElementPreview {
HomeTopBar( HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats, selectedNavigationItem = HomeNavigationBarItem.Chats,
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = false, showAvatarIndicator = false,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
@ -322,6 +379,7 @@ internal fun HomeTopBarPreview() = ElementPreview {
canReportBug = true, canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
spaceFiltersState = anUnselectedSpaceFiltersState(),
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }
@ -332,7 +390,6 @@ internal fun HomeTopBarPreview() = ElementPreview {
internal fun HomeTopBarSpacesPreview() = ElementPreview { internal fun HomeTopBarSpacesPreview() = ElementPreview {
HomeTopBar( HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Spaces, selectedNavigationItem = HomeNavigationBarItem.Spaces,
title = stringResource(R.string.screen_home_tab_spaces),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = false, showAvatarIndicator = false,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
@ -345,6 +402,7 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
canReportBug = true, canReportBug = true,
displayFilters = false, displayFilters = false,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
spaceFiltersState = anUnselectedSpaceFiltersState(),
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }
@ -355,7 +413,6 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview { internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
HomeTopBar( HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats, selectedNavigationItem = HomeNavigationBarItem.Chats,
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = true, showAvatarIndicator = true,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
@ -368,6 +425,7 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
canReportBug = true, canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
spaceFiltersState = anUnselectedSpaceFiltersState(),
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }
@ -378,7 +436,6 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
internal fun HomeTopBarMultiAccountPreview() = ElementPreview { internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
HomeTopBar( HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats, selectedNavigationItem = HomeNavigationBarItem.Chats,
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(), currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(),
showAvatarIndicator = false, showAvatarIndicator = false,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
@ -391,6 +448,30 @@ internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
canReportBug = true, canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
spaceFiltersState = anUnselectedSpaceFiltersState(),
onMenuActionClick = {},
)
}
@OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight
@Composable
internal fun HomeTopSpaceFiltersSelectedPreview() = ElementPreview {
HomeTopBar(
selectedNavigationItem = HomeNavigationBarItem.Chats,
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {},
onAccountSwitch = {},
onToggleSearch = {},
onCreateSpace = {},
canCreateSpaces = true,
canReportBug = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
spaceFiltersState = aSelectedSpaceFiltersState(),
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }

View file

@ -14,8 +14,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
@ -24,7 +26,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@Inject @ContributesBinding(SessionScope::class)
class SpaceFiltersPresenter( class SpaceFiltersPresenter(
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,

View file

@ -18,10 +18,8 @@ import kotlinx.collections.immutable.persistentListOf
class SpaceFiltersStateProvider : PreviewParameterProvider<SpaceFiltersState> { class SpaceFiltersStateProvider : PreviewParameterProvider<SpaceFiltersState> {
override val values: Sequence<SpaceFiltersState> override val values: Sequence<SpaceFiltersState>
get() = sequenceOf( get() = sequenceOf(
aDisabledSpaceFiltersState(),
anUnselectedSpaceFiltersState(),
aSelectingSpaceFiltersState(), aSelectingSpaceFiltersState(),
aSelectedSpaceFiltersState(), aSelectingSpaceFiltersState(searchQuery = "Pr")
) )
} }
@ -54,11 +52,11 @@ fun aSelectingSpaceFiltersState(
roomId = RoomId("!gaming:example.com"), roomId = RoomId("!gaming:example.com"),
), ),
), ),
searchQuery: TextFieldState = TextFieldState(), searchQuery: String = "",
eventSink: (SpaceFiltersEvent.Selecting) -> Unit = {}, eventSink: (SpaceFiltersEvent.Selecting) -> Unit = {},
) = SpaceFiltersState.Selecting( ) = SpaceFiltersState.Selecting(
availableFilters = persistentListOf(*availableFilters.toTypedArray()), availableFilters = persistentListOf(*availableFilters.toTypedArray()),
searchQuery = searchQuery, searchQuery = TextFieldState(searchQuery),
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
@ -32,9 +33,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.home.impl.R
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.avatar.AvatarType
@ -97,16 +100,20 @@ private fun SpaceFiltersBottomSheetContent(
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 16.dp) .fillMaxHeight(0.9f)
.padding(vertical = 16.dp)
) { ) {
Text( Text(
text = "Your spaces", text = stringResource(R.string.screen_roomlist_your_spaces),
style = ElementTheme.typography.fontHeadingSmMedium, style = ElementTheme.typography.fontHeadingSmMedium,
modifier = Modifier.padding(horizontal = 16.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
SearchField( SearchField(
state = searchQuery, state = searchQuery,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
placeholder = stringResource(CommonStrings.action_search), placeholder = stringResource(CommonStrings.action_search),
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@ -149,12 +156,16 @@ private fun SpaceFilterItem(
text = spaceRoom.displayName, text = spaceRoom.displayName,
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary, color = ElementTheme.colors.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
if (supportingText != null) { if (supportingText != null) {
Text( Text(
text = supportingText, text = supportingText,
style = ElementTheme.typography.fontBodyMdRegular, style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary, color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
} }
} }