RoomListFilters: extract logic of selection/deselection and tweak a bit the animations.
This commit is contained in:
parent
dc5f93ef2f
commit
2da9bc198a
11 changed files with 211 additions and 96 deletions
|
|
@ -20,12 +20,12 @@ import io.element.android.features.roomlist.impl.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum class representing the different filters that can be applied to the room list.
|
* Enum class representing the different filters that can be applied to the room list.
|
||||||
* Order is important.
|
* Order is important, it'll be used as initial order in the UI.
|
||||||
*/
|
*/
|
||||||
enum class RoomListFilter(val stringResource: Int) {
|
enum class RoomListFilter(val stringResource: Int) {
|
||||||
Rooms(R.string.screen_roomlist_filter_rooms),
|
|
||||||
People(R.string.screen_roomlist_filter_people),
|
|
||||||
Unread(R.string.screen_roomlist_filter_unreads),
|
Unread(R.string.screen_roomlist_filter_unreads),
|
||||||
|
People(R.string.screen_roomlist_filter_people),
|
||||||
|
Rooms(R.string.screen_roomlist_filter_rooms),
|
||||||
Favourites(R.string.screen_roomlist_filter_favourites);
|
Favourites(R.string.screen_roomlist_filter_favourites);
|
||||||
|
|
||||||
val oppositeFilter: RoomListFilter?
|
val oppositeFilter: RoomListFilter?
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@
|
||||||
package io.element.android.features.roomlist.impl.filters
|
package io.element.android.features.roomlist.impl.filters
|
||||||
|
|
||||||
sealed interface RoomListFiltersEvents {
|
sealed interface RoomListFiltersEvents {
|
||||||
data object ClearSelectedFilters : RoomListFiltersEvents
|
|
||||||
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents
|
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents
|
||||||
|
data object ClearSelectedFilters : RoomListFiltersEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionStrategy
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
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
|
||||||
|
|
@ -34,50 +32,39 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as Matrix
|
||||||
class RoomListFiltersPresenter @Inject constructor(
|
class RoomListFiltersPresenter @Inject constructor(
|
||||||
private val roomListService: RoomListService,
|
private val roomListService: RoomListService,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val filterSelectionStrategy: FilterSelectionStrategy,
|
||||||
) : Presenter<RoomListFiltersState> {
|
) : Presenter<RoomListFiltersState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListFiltersState {
|
override fun present(): RoomListFiltersState {
|
||||||
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false)
|
|
||||||
var unselectedFilters: Set<RoomListFilter> by rememberSaveable {
|
|
||||||
mutableStateOf(RoomListFilter.entries.toSet())
|
|
||||||
}
|
|
||||||
var selectedFilters: Set<RoomListFilter> by rememberSaveable {
|
|
||||||
mutableStateOf(emptySet())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateFilters(newSelectedFilters: Set<RoomListFilter>) {
|
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false)
|
||||||
selectedFilters = newSelectedFilters
|
val filters by filterSelectionStrategy.filterSelectionStates.collectAsState()
|
||||||
unselectedFilters = RoomListFilter.entries.toSet() -
|
|
||||||
selectedFilters -
|
|
||||||
selectedFilters.mapNotNull { it.oppositeFilter }.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvents(event: RoomListFiltersEvents) {
|
fun handleEvents(event: RoomListFiltersEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is RoomListFiltersEvents.ToggleFilter -> {
|
|
||||||
val newSelectedFilters = if (selectedFilters.contains(event.filter)) {
|
|
||||||
selectedFilters - event.filter
|
|
||||||
} else {
|
|
||||||
selectedFilters + event.filter
|
|
||||||
}
|
|
||||||
updateFilters(newSelectedFilters)
|
|
||||||
}
|
|
||||||
RoomListFiltersEvents.ClearSelectedFilters -> {
|
RoomListFiltersEvents.ClearSelectedFilters -> {
|
||||||
updateFilters(newSelectedFilters = emptySet())
|
filterSelectionStrategy.clear()
|
||||||
|
}
|
||||||
|
is RoomListFiltersEvents.ToggleFilter -> {
|
||||||
|
filterSelectionStrategy.toggle(event.filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(isFeatureEnabled) {
|
LaunchedEffect(isFeatureEnabled) {
|
||||||
if (!isFeatureEnabled) {
|
if (!isFeatureEnabled) {
|
||||||
updateFilters(emptySet())
|
filterSelectionStrategy.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(selectedFilters) {
|
LaunchedEffect(filters) {
|
||||||
val allRoomsFilter = MatrixRoomListFilter.All(
|
val allRoomsFilter = MatrixRoomListFilter.All(
|
||||||
selectedFilters.map { roomListFilter ->
|
filters
|
||||||
when (roomListFilter) {
|
.filter { it.isSelected }
|
||||||
|
.map { roomListFilter ->
|
||||||
|
when (roomListFilter.filter) {
|
||||||
RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group
|
RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group
|
||||||
RoomListFilter.People -> MatrixRoomListFilter.Category.People
|
RoomListFilter.People -> MatrixRoomListFilter.Category.People
|
||||||
RoomListFilter.Unread -> MatrixRoomListFilter.Unread
|
RoomListFilter.Unread -> MatrixRoomListFilter.Unread
|
||||||
|
|
@ -88,9 +75,9 @@ class RoomListFiltersPresenter @Inject constructor(
|
||||||
roomListService.allRooms.updateFilter(allRoomsFilter)
|
roomListService.allRooms.updateFilter(allRoomsFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return RoomListFiltersState(
|
return RoomListFiltersState(
|
||||||
unselectedFilters = unselectedFilters.toPersistentList(),
|
filterSelectionStates = filters.toPersistentList(),
|
||||||
selectedFilters = selectedFilters.toPersistentList(),
|
|
||||||
isFeatureEnabled = isFeatureEnabled,
|
isFeatureEnabled = isFeatureEnabled,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.filters
|
package io.element.android.features.roomlist.impl.filters
|
||||||
|
|
||||||
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class RoomListFiltersState(
|
data class RoomListFiltersState(
|
||||||
val unselectedFilters: ImmutableList<RoomListFilter>,
|
val filterSelectionStates: ImmutableList<FilterSelectionState>,
|
||||||
val selectedFilters: ImmutableList<RoomListFilter>,
|
|
||||||
val isFeatureEnabled: Boolean,
|
val isFeatureEnabled: Boolean,
|
||||||
val eventSink: (RoomListFiltersEvents) -> Unit,
|
val eventSink: (RoomListFiltersEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
val hasAnyFilterSelected = selectedFilters.isNotEmpty()
|
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
package io.element.android.features.roomlist.impl.filters
|
package io.element.android.features.roomlist.impl.filters
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> {
|
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> {
|
||||||
|
|
@ -26,20 +25,17 @@ class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersSta
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomListFiltersState(),
|
aRoomListFiltersState(),
|
||||||
aRoomListFiltersState(
|
aRoomListFiltersState(
|
||||||
selectedFilters = persistentListOf(RoomListFilter.Rooms, RoomListFilter.Favourites),
|
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) }
|
||||||
unselectedFilters = persistentListOf(RoomListFilter.Unread),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aRoomListFiltersState(
|
fun aRoomListFiltersState(
|
||||||
unselectedFilters: ImmutableList<RoomListFilter> = RoomListFilter.entries.toImmutableList(),
|
filterSelectionStates: List<FilterSelectionState> = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = false) },
|
||||||
selectedFilters: ImmutableList<RoomListFilter> = persistentListOf(),
|
|
||||||
isFeatureEnabled: Boolean = true,
|
isFeatureEnabled: Boolean = true,
|
||||||
eventSink: (RoomListFiltersEvents) -> Unit = {},
|
eventSink: (RoomListFiltersEvents) -> Unit = {},
|
||||||
) = RoomListFiltersState(
|
) = RoomListFiltersState(
|
||||||
unselectedFilters = unselectedFilters,
|
filterSelectionStates = filterSelectionStates.toImmutableList(),
|
||||||
selectedFilters = selectedFilters,
|
|
||||||
isFeatureEnabled = isFeatureEnabled,
|
isFeatureEnabled = isFeatureEnabled,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.roomlist.impl.filters
|
package io.element.android.features.roomlist.impl.filters
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -25,7 +26,6 @@ import androidx.compose.foundation.layout.Row
|
||||||
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.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.FilterChipDefaults
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -51,18 +52,19 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomListFiltersView(
|
fun RoomListFiltersView(
|
||||||
state: RoomListFiltersState,
|
state: RoomListFiltersState,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun onClearFiltersClicked() {
|
fun onClearFiltersClicked() {
|
||||||
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
|
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFilterClicked(filter: RoomListFilter) {
|
fun onToggleFilter(filter: RoomListFilter) {
|
||||||
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter))
|
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,12 +73,14 @@ fun RoomListFiltersView(
|
||||||
modifier = modifier.padding(start = startPadding, end = 16.dp),
|
modifier = modifier.padding(start = startPadding, end = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
AnimatedVisibility(visible = state.hasAnyFilterSelected) {
|
AnimatedVisibility(visible = state.hasAnyFilterSelected) {
|
||||||
RoomListClearFiltersButton(
|
RoomListClearFiltersButton(
|
||||||
modifier = Modifier.testTag(TestTags.homeScreenClearFilters),
|
modifier = Modifier.testTag(TestTags.homeScreenClearFilters),
|
||||||
onClick = ::onClearFiltersClicked
|
onClick = ::onClearFiltersClicked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
val fadingEdgesBrush = horizontalFadingEdgesBrush(
|
val fadingEdgesBrush = horizontalFadingEdgesBrush(
|
||||||
showLeft = lazyListState.canScrollBackward,
|
showLeft = lazyListState.canScrollBackward,
|
||||||
|
|
@ -89,26 +93,25 @@ fun RoomListFiltersView(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
roomListFilters(state.selectedFilters, selected = true, onClick = ::onFilterClicked)
|
items(
|
||||||
roomListFilters(state.unselectedFilters, selected = false, onClick = ::onFilterClicked)
|
items = state.filterSelectionStates,
|
||||||
|
key = { it.filter.ordinal }
|
||||||
|
) { filterWithSelection ->
|
||||||
|
RoomListFilterView(
|
||||||
|
modifier = Modifier.animateItemPlacement(
|
||||||
|
animationSpec = tween(200),
|
||||||
|
),
|
||||||
|
roomListFilter = filterWithSelection.filter,
|
||||||
|
selected = filterWithSelection.isSelected,
|
||||||
|
onClick = ::onToggleFilter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(state.filterSelectionStates) {
|
||||||
|
if (lazyListState.canScrollBackward) {
|
||||||
|
lazyListState.animateScrollToItem(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
private fun LazyListScope.roomListFilters(
|
|
||||||
filters: ImmutableList<RoomListFilter>,
|
|
||||||
selected: Boolean,
|
|
||||||
onClick: (RoomListFilter) -> Unit,
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = filters,
|
|
||||||
) { filter ->
|
|
||||||
RoomListFilterView(
|
|
||||||
roomListFilter = filter,
|
|
||||||
selected = selected,
|
|
||||||
onClick = onClick,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.filters.selection
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
class DefaultFilterSelectionStrategy @Inject constructor() : FilterSelectionStrategy {
|
||||||
|
|
||||||
|
private val selectedFilters = LinkedHashSet<RoomListFilter>()
|
||||||
|
|
||||||
|
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
||||||
|
|
||||||
|
override fun select(filter: RoomListFilter) {
|
||||||
|
selectedFilters.add(filter)
|
||||||
|
filterSelectionStates.value = buildFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deselect(filter: RoomListFilter) {
|
||||||
|
selectedFilters.remove(filter)
|
||||||
|
filterSelectionStates.value = buildFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSelected(filter: RoomListFilter): Boolean {
|
||||||
|
return selectedFilters.contains(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
selectedFilters.clear()
|
||||||
|
filterSelectionStates.value = buildFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildFilters(): Set<FilterSelectionState> {
|
||||||
|
val selectedFilterStates = selectedFilters.map {
|
||||||
|
FilterSelectionState(
|
||||||
|
filter = it,
|
||||||
|
isSelected = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val unselectedFilters = RoomListFilter.entries - selectedFilters - selectedFilters.mapNotNull { it.oppositeFilter }.toSet()
|
||||||
|
val unselectedFilterStates = unselectedFilters.map {
|
||||||
|
FilterSelectionState(
|
||||||
|
filter = it,
|
||||||
|
isSelected = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (selectedFilterStates + unselectedFilterStates).toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.filters.selection
|
||||||
|
|
||||||
|
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||||
|
|
||||||
|
data class FilterSelectionState(
|
||||||
|
val filter: RoomListFilter,
|
||||||
|
val isSelected: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.filters.selection
|
||||||
|
|
||||||
|
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
interface FilterSelectionStrategy {
|
||||||
|
|
||||||
|
val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
|
||||||
|
|
||||||
|
fun select(filter: RoomListFilter)
|
||||||
|
fun deselect(filter: RoomListFilter)
|
||||||
|
fun isSelected(filter: RoomListFilter): Boolean
|
||||||
|
fun clear()
|
||||||
|
|
||||||
|
fun toggle(filter: RoomListFilter) {
|
||||||
|
if (isSelected(filter)) {
|
||||||
|
deselect(filter)
|
||||||
|
} else {
|
||||||
|
select(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.roomlist.impl.filters.selection.DefaultFilterSelectionStrategy
|
||||||
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
|
@ -37,13 +39,12 @@ class RoomListFiltersPresenterTests {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
awaitItem().let { state ->
|
awaitItem().let { state ->
|
||||||
assertThat(state.selectedFilters).isEmpty()
|
|
||||||
assertThat(state.hasAnyFilterSelected).isFalse()
|
assertThat(state.hasAnyFilterSelected).isFalse()
|
||||||
assertThat(state.unselectedFilters).containsExactly(
|
assertThat(state.filterSelectionStates).containsExactly(
|
||||||
RoomListFilter.Rooms,
|
filterSelectionState(RoomListFilter.Unread, false),
|
||||||
RoomListFilter.People,
|
filterSelectionState(RoomListFilter.People, false),
|
||||||
RoomListFilter.Unread,
|
filterSelectionState(RoomListFilter.Rooms, false),
|
||||||
RoomListFilter.Favourites,
|
filterSelectionState(RoomListFilter.Favourites, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
|
@ -58,32 +59,29 @@ class RoomListFiltersPresenterTests {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
||||||
|
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
|
|
||||||
assertThat(state.selectedFilters).containsExactly(RoomListFilter.Rooms)
|
|
||||||
assertThat(state.hasAnyFilterSelected).isTrue()
|
assertThat(state.hasAnyFilterSelected).isTrue()
|
||||||
assertThat(state.unselectedFilters).containsExactly(
|
assertThat(state.filterSelectionStates).containsExactly(
|
||||||
RoomListFilter.Unread,
|
filterSelectionState(RoomListFilter.Rooms, true),
|
||||||
RoomListFilter.Favourites,
|
filterSelectionState(RoomListFilter.Unread, false),
|
||||||
)
|
filterSelectionState(RoomListFilter.Favourites, false),
|
||||||
|
).inOrder()
|
||||||
|
|
||||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
||||||
assertThat(roomListCurrentFilter.filters).containsExactly(
|
assertThat(roomListCurrentFilter.filters).containsExactly(
|
||||||
MatrixRoomListFilter.Category.Group,
|
MatrixRoomListFilter.Category.Group,
|
||||||
)
|
)
|
||||||
|
|
||||||
state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
assertThat(state.selectedFilters).isEmpty()
|
|
||||||
assertThat(state.hasAnyFilterSelected).isFalse()
|
assertThat(state.hasAnyFilterSelected).isFalse()
|
||||||
assertThat(state.unselectedFilters).containsExactly(
|
assertThat(state.filterSelectionStates).containsExactly(
|
||||||
RoomListFilter.Rooms,
|
filterSelectionState(RoomListFilter.Unread, false),
|
||||||
RoomListFilter.People,
|
filterSelectionState(RoomListFilter.People, false),
|
||||||
RoomListFilter.Unread,
|
filterSelectionState(RoomListFilter.Rooms, false),
|
||||||
RoomListFilter.Favourites,
|
filterSelectionState(RoomListFilter.Favourites, false),
|
||||||
)
|
).inOrder()
|
||||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
||||||
assertThat(roomListCurrentFilter.filters).isEmpty()
|
assertThat(roomListCurrentFilter.filters).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
@ -99,24 +97,28 @@ class RoomListFiltersPresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
assertThat(state.selectedFilters).isNotEmpty()
|
|
||||||
assertThat(state.hasAnyFilterSelected).isTrue()
|
assertThat(state.hasAnyFilterSelected).isTrue()
|
||||||
state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters)
|
state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters)
|
||||||
}
|
}
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
assertThat(state.selectedFilters).isEmpty()
|
|
||||||
assertThat(state.hasAnyFilterSelected).isFalse()
|
assertThat(state.hasAnyFilterSelected).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoomListFiltersPresenter(
|
private fun filterSelectionState(filter: RoomListFilter, selected: Boolean) = FilterSelectionState(
|
||||||
|
filter = filter,
|
||||||
|
isSelected = selected,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createRoomListFiltersPresenter(
|
||||||
roomListService: RoomListService = FakeRoomListService(),
|
roomListService: RoomListService = FakeRoomListService(),
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||||
): RoomListFiltersPresenter {
|
): RoomListFiltersPresenter {
|
||||||
return RoomListFiltersPresenter(
|
return RoomListFiltersPresenter(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
|
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,11 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.roomlist.impl.R
|
import io.element.android.features.roomlist.impl.R
|
||||||
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
@ -56,8 +55,7 @@ class RoomListFiltersViewTests {
|
||||||
rule.setContent {
|
rule.setContent {
|
||||||
RoomListFiltersView(
|
RoomListFiltersView(
|
||||||
state = aRoomListFiltersState(
|
state = aRoomListFiltersState(
|
||||||
unselectedFilters = persistentListOf(),
|
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) },
|
||||||
selectedFilters = RoomListFilter.entries.toImmutableList(),
|
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue