Handle SpaceFilter interaction with other RoomListFilters

This commit is contained in:
ganfra 2026-02-03 21:22:56 +01:00
parent 218aafa05a
commit 23498bbaee
9 changed files with 67 additions and 21 deletions

View file

@ -228,6 +228,7 @@ private fun HomeScaffold(
RoomListContentView( RoomListContentView(
contentState = roomListState.contentState, contentState = roomListState.contentState,
filtersState = roomListState.filtersState, filtersState = roomListState.filtersState,
spaceFiltersState = roomListState.spaceFiltersState,
lazyListState = roomsLazyListState, lazyListState = roomsLazyListState,
hideInvitesAvatars = roomListState.hideInvitesAvatars, hideInvitesAvatars = roomListState.hideInvitesAvatars,
eventSink = roomListState.eventSink, eventSink = roomListState.eventSink,

View file

@ -45,6 +45,8 @@ import io.element.android.features.home.impl.roomlist.RoomListContentState
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
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.SecurityBannerState import io.element.android.features.home.impl.roomlist.SecurityBannerState
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Button
@ -59,6 +61,7 @@ import kotlinx.collections.immutable.ImmutableList
fun RoomListContentView( fun RoomListContentView(
contentState: RoomListContentState, contentState: RoomListContentState,
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
spaceFiltersState: SpaceFiltersState,
lazyListState: LazyListState, lazyListState: LazyListState,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
eventSink: (RoomListEvent) -> Unit, eventSink: (RoomListEvent) -> Unit,
@ -93,6 +96,7 @@ fun RoomListContentView(
state = contentState, state = contentState,
hideInvitesAvatars = hideInvitesAvatars, hideInvitesAvatars = hideInvitesAvatars,
filtersState = filtersState, filtersState = filtersState,
spaceFiltersState = spaceFiltersState,
eventSink = eventSink, eventSink = eventSink,
onSetUpRecoveryClick = onSetUpRecoveryClick, onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
@ -172,6 +176,7 @@ private fun RoomsView(
state: RoomListContentState.Rooms, state: RoomListContentState.Rooms,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
spaceFiltersState: SpaceFiltersState,
eventSink: (RoomListEvent) -> Unit, eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
@ -180,9 +185,12 @@ private fun RoomsView(
lazyListState: LazyListState, lazyListState: LazyListState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) { val isSpaceFilterSelected = spaceFiltersState is SpaceFiltersState.Selected
val hasAnyFilterSelected = filtersState.hasAnyFilterSelected || isSpaceFilterSelected
if (state.summaries.isEmpty() && hasAnyFilterSelected) {
EmptyViewForFilterStates( EmptyViewForFilterStates(
selectedFilters = filtersState.selectedFilters(), selectedFilters = filtersState.selectedFilters(),
isSpaceFilterSelected = isSpaceFilterSelected,
modifier = modifier.fillMaxSize() modifier = modifier.fillMaxSize()
) )
} else { } else {
@ -278,9 +286,10 @@ private fun RoomsViewList(
@Composable @Composable
private fun EmptyViewForFilterStates( private fun EmptyViewForFilterStates(
selectedFilters: ImmutableList<RoomListFilter>, selectedFilters: ImmutableList<RoomListFilter>,
isSpaceFilterSelected: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) ?: return val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected) ?: return
EmptyScaffold( EmptyScaffold(
title = emptyStateResources.title, title = emptyStateResources.title,
subtitle = emptyStateResources.subtitle, subtitle = emptyStateResources.subtitle,
@ -331,6 +340,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
) )
} }
), ),
spaceFiltersState = anUnselectedSpaceFiltersState(),
hideInvitesAvatars = false, hideInvitesAvatars = false,
eventSink = {}, eventSink = {},
onSetUpRecoveryClick = {}, onSetUpRecoveryClick = {},

View file

@ -24,8 +24,12 @@ data class RoomListFiltersEmptyStateResources(
/** /**
* Create a [RoomListFiltersEmptyStateResources] from a list of selected filters. * Create a [RoomListFiltersEmptyStateResources] from a list of selected filters.
*/ */
fun fromSelectedFilters(selectedFilters: List<RoomListFilter>): RoomListFiltersEmptyStateResources? { fun fromSelectedFilters(selectedFilters: List<RoomListFilter>, isSpaceFilterSelected: Boolean): RoomListFiltersEmptyStateResources? {
return when { return when {
isSpaceFilterSelected -> RoomListFiltersEmptyStateResources(
title = R.string.screen_roomlist_filter_mixed_empty_state_title,
subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle
)
selectedFilters.isEmpty() -> null selectedFilters.isEmpty() -> null
selectedFilters.size == 1 -> { selectedFilters.size == 1 -> {
when (selectedFilters.first()) { when (selectedFilters.first()) {

View file

@ -11,4 +11,5 @@ package io.element.android.features.home.impl.filters
sealed interface RoomListFiltersEvent { sealed interface RoomListFiltersEvent {
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvent data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvent
data object ClearSelectedFilters : RoomListFiltersEvent data object ClearSelectedFilters : RoomListFiltersEvent
data class SetHiddenFilter(val filters: Set<RoomListFilter>): RoomListFiltersEvent
} }

View file

@ -30,6 +30,9 @@ class RoomListFiltersPresenter(
is RoomListFiltersEvent.ToggleFilter -> { is RoomListFiltersEvent.ToggleFilter -> {
filterSelectionStrategy.toggle(event.filter) filterSelectionStrategy.toggle(event.filter)
} }
is RoomListFiltersEvent.SetHiddenFilter -> {
filterSelectionStrategy.setHiddenFilters(event.filters)
}
} }
} }

View file

@ -15,17 +15,29 @@ import kotlinx.coroutines.flow.MutableStateFlow
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
class DefaultFilterSelectionStrategy : FilterSelectionStrategy { class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
private val selectedFilters = LinkedHashSet<RoomListFilter>() private val _selectedFilters = LinkedHashSet<RoomListFilter>()
private val hiddenFilters = LinkedHashSet<RoomListFilter>()
private val selectedFilters
get() = _selectedFilters - hiddenFilters
private val availableFilters
get() = RoomListFilter.entries.toSet() - hiddenFilters
override val filterSelectionStates = MutableStateFlow(buildFilters()) override val filterSelectionStates = MutableStateFlow(buildFilters())
override fun setHiddenFilters(filters: Set<RoomListFilter>) {
hiddenFilters.clear()
hiddenFilters.addAll(filters)
filterSelectionStates.value = buildFilters()
}
override fun select(filter: RoomListFilter) { override fun select(filter: RoomListFilter) {
selectedFilters.add(filter) _selectedFilters.add(filter)
filterSelectionStates.value = buildFilters() filterSelectionStates.value = buildFilters()
} }
override fun deselect(filter: RoomListFilter) { override fun deselect(filter: RoomListFilter) {
selectedFilters.remove(filter) _selectedFilters.remove(filter)
filterSelectionStates.value = buildFilters() filterSelectionStates.value = buildFilters()
} }
@ -34,7 +46,7 @@ class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
} }
override fun clear() { override fun clear() {
selectedFilters.clear() _selectedFilters.clear()
filterSelectionStates.value = buildFilters() filterSelectionStates.value = buildFilters()
} }
@ -45,7 +57,7 @@ class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
isSelected = true isSelected = true
) )
} }
val unselectedFilters = RoomListFilter.entries - selectedFilters - selectedFilters.flatMap { it.incompatibleFilters }.toSet() val unselectedFilters = availableFilters - selectedFilters - selectedFilters.flatMap { it.incompatibleFilters }.toSet()
val unselectedFilterStates = unselectedFilters.map { val unselectedFilterStates = unselectedFilters.map {
FilterSelectionState( FilterSelectionState(
filter = it, filter = it,

View file

@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
interface FilterSelectionStrategy { interface FilterSelectionStrategy {
val filterSelectionStates: StateFlow<Set<FilterSelectionState>> val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
fun setHiddenFilters(filters: Set<RoomListFilter>)
fun select(filter: RoomListFilter) fun select(filter: RoomListFilter)
fun deselect(filter: RoomListFilter) fun deselect(filter: RoomListFilter)
fun isSelected(filter: RoomListFilter): Boolean fun isSelected(filter: RoomListFilter): Boolean

View file

@ -28,6 +28,9 @@ import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.announcement.api.Announcement import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService import io.element.android.features.announcement.api.AnnouncementService
import io.element.android.features.home.impl.datasource.RoomListDataSource import io.element.android.features.home.impl.datasource.RoomListDataSource
import io.element.android.features.home.impl.filters.RoomListFilter.People
import io.element.android.features.home.impl.filters.RoomListFilter.Rooms
import io.element.android.features.home.impl.filters.RoomListFiltersEvent
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.into import io.element.android.features.home.impl.filters.into
import io.element.android.features.home.impl.search.RoomListSearchEvent import io.element.android.features.home.impl.search.RoomListSearchEvent
@ -156,13 +159,16 @@ class RoomListPresenter(
} }
} }
LaunchedEffect(filtersState.filterSelectionStates, spaceFiltersState.selectedFilter()) { LaunchedEffect(spaceFiltersState.selectedFilter()) {
val selectedFilters = filtersState.filterSelectionStates.mapNotNull { filterState -> val hiddenFilters = if (spaceFiltersState is SpaceFiltersState.Selected) {
if (!filterState.isSelected) { setOf(People, Rooms)
return@mapNotNull null } else {
} emptySet()
filterState.filter.into()
} }
filtersState.eventSink(RoomListFiltersEvent.SetHiddenFilter(hiddenFilters))
}
LaunchedEffect(filtersState.filterSelectionStates, spaceFiltersState.selectedFilter()) {
val selectedFilters = filtersState.selectedFilters().map { filter -> filter.into() }
val selectedSpaceFilter = when (spaceFiltersState) { val selectedSpaceFilter = when (spaceFiltersState) {
is SpaceFiltersState.Selected -> RoomListFilter.Identifiers(spaceFiltersState.selectedFilter.descendants) is SpaceFiltersState.Selected -> RoomListFilter.Identifiers(spaceFiltersState.selectedFilter.descendants)
else -> null else -> null

View file

@ -16,14 +16,14 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return null when selectedFilters is empty`() { fun `fromSelectedFilters should return null when selectedFilters is empty`() {
val selectedFilters = emptyList<RoomListFilter>() val selectedFilters = emptyList<RoomListFilter>()
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNull() assertThat(result).isNull()
} }
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only unread filter`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only unread filter`() {
val selectedFilters = listOf(RoomListFilter.Unread) val selectedFilters = listOf(RoomListFilter.Unread)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_unreads_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_unreads_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
@ -32,7 +32,7 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only people filter`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only people filter`() {
val selectedFilters = listOf(RoomListFilter.People) val selectedFilters = listOf(RoomListFilter.People)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_people_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_people_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
@ -41,7 +41,7 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only rooms filter`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only rooms filter`() {
val selectedFilters = listOf(RoomListFilter.Rooms) val selectedFilters = listOf(RoomListFilter.Rooms)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_rooms_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_rooms_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
@ -50,7 +50,7 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only favourites filter`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only favourites filter`() {
val selectedFilters = listOf(RoomListFilter.Favourites) val selectedFilters = listOf(RoomListFilter.Favourites)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_subtitle)
@ -59,7 +59,7 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only invites filter`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only invites filter`() {
val selectedFilters = listOf(RoomListFilter.Invites) val selectedFilters = listOf(RoomListFilter.Invites)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_invites_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_invites_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
@ -68,7 +68,15 @@ class RoomListFiltersEmptyStateResourcesTest {
@Test @Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has multiple filters`() { fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has multiple filters`() {
val selectedFilters = listOf(RoomListFilter.Unread, RoomListFilter.People, RoomListFilter.Rooms, RoomListFilter.Favourites) val selectedFilters = listOf(RoomListFilter.Unread, RoomListFilter.People, RoomListFilter.Rooms, RoomListFilter.Favourites)
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
}
@Test
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when isSpaceFilterSelected is true`() {
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(emptyList(), isSpaceFilterSelected = true)
assertThat(result).isNotNull() assertThat(result).isNotNull()
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title) assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title)
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle) assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)