mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Ignore a room list filter that is incompatible with an already selected one
Some room list filters are mutually exclusive, and the UI expresses that by hiding the incompatible chips rather than disabling them. Nothing enforced the rule on the write path, so two chips tapped in the same frame, before the list could recompose, both applied. The room list then matched nothing while the offending chip was hidden, leaving no way to see or undo what was filtering the rooms. DefaultFilterSelectionStrategy.select() now ignores a filter that is incompatible with one already selected, so the same table buildFilters() uses to hide chips also governs the selection itself. The currently applied filters win and the late tap is dropped, which matches the existing design where an incompatible chip is already on its way out of the UI when it is tapped. toggle(), deselect(), clear() and the FilterSelectionStrategy interface are unchanged. Relates to https://github.com/element-hq/element-x-android/issues/5383
This commit is contained in:
+5
@@ -22,6 +22,11 @@ class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
|
||||
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
||||
|
||||
override fun select(filter: RoomListFilter) {
|
||||
// Incompatible filters are hidden from the UI rather than disabled, so a filter can still be
|
||||
// selected while its chip is on its way out, typically when two chips are tapped in the same
|
||||
// frame. Ignore it, the currently applied filters win.
|
||||
// See https://github.com/element-hq/element-x-android/issues/5383
|
||||
if (selectedFilters.any { it in filter.incompatibleFilters }) return
|
||||
selectedFilters.add(filter)
|
||||
filterSelectionStates.value = buildFilters()
|
||||
}
|
||||
|
||||
+45
@@ -72,6 +72,51 @@ class RoomListFiltersPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun `present - toggling two incompatible filters in the same frame keeps the first one`() = runTest {
|
||||
val presenter = createRoomListFiltersPresenter()
|
||||
presenter.test {
|
||||
awaitItem().let { state ->
|
||||
// Both events are sent on the same state, i.e. before any recomposition, which is what
|
||||
// happens when the two chips are tapped simultaneously.
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Favourites))
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Invites))
|
||||
}
|
||||
advanceUntilIdle()
|
||||
awaitLastSequentialItem().let { state ->
|
||||
assertThat(state.selectedFilters()).containsExactly(RoomListFilter.Favourites)
|
||||
assertThat(state.filterSelectionStates).containsExactly(
|
||||
filterSelectionState(RoomListFilter.Favourites, true),
|
||||
filterSelectionState(RoomListFilter.Unread, false),
|
||||
filterSelectionState(RoomListFilter.People, false),
|
||||
filterSelectionState(RoomListFilter.Rooms, false),
|
||||
).inOrder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun `present - toggling two incompatible categories in the same frame keeps the first one`() = runTest {
|
||||
val presenter = createRoomListFiltersPresenter()
|
||||
presenter.test {
|
||||
awaitItem().let { state ->
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.People))
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||
}
|
||||
advanceUntilIdle()
|
||||
awaitLastSequentialItem().let { state ->
|
||||
assertThat(state.selectedFilters()).containsExactly(RoomListFilter.People)
|
||||
assertThat(state.filterSelectionStates).containsExactly(
|
||||
filterSelectionState(RoomListFilter.People, true),
|
||||
filterSelectionState(RoomListFilter.Unread, false),
|
||||
filterSelectionState(RoomListFilter.Favourites, false),
|
||||
).inOrder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun `present - clear filters event`() = runTest {
|
||||
|
||||
Reference in New Issue
Block a user