diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 69a1b4cf9a..245065a52f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -37,6 +37,7 @@ import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter import io.element.android.features.roomlist.impl.search.RoomListSearchEvents import io.element.android.features.roomlist.impl.search.RoomListSearchState @@ -82,6 +83,7 @@ class RoomListPresenter @Inject constructor( private val roomListDataSource: RoomListDataSource, private val featureFlagService: FeatureFlagService, private val indicatorService: IndicatorService, + private val filtersPresenter: Presenter, private val searchPresenter: Presenter, private val migrationScreenPresenter: MigrationScreenPresenter, private val sessionPreferencesStore: SessionPreferencesStore, @@ -102,6 +104,8 @@ class RoomListPresenter @Inject constructor( roomListDataSource.allRooms.collect { value = AsyncData.Success(it) } } val networkConnectionStatus by networkMonitor.connectivity.collectAsState() + + val filtersState = filtersPresenter.present() val searchState = searchPresenter.present() LaunchedEffect(Unit) { @@ -168,9 +172,10 @@ class RoomListPresenter @Inject constructor( invitesState = inviteStateDataSource.inviteState(), contextMenu = contextMenu.value, leaveRoomState = leaveRoomState, + filtersState = filtersState, searchState = searchState, displayMigrationStatus = isMigrating, - eventSink = ::handleEvents + eventSink = ::handleEvents, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 076f6bb10e..c4581732eb 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl import androidx.compose.runtime.Immutable import io.element.android.features.leaveroom.api.LeaveRoomState +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.search.RoomListSearchState import io.element.android.libraries.architecture.AsyncData @@ -37,10 +38,12 @@ data class RoomListState( val invitesState: InvitesState, val contextMenu: ContextMenu, val leaveRoomState: LeaveRoomState, + val filtersState: RoomListFiltersState, val searchState: RoomListSearchState, val displayMigrationStatus: Boolean, val eventSink: (RoomListEvents) -> Unit, ) { + val displayFilters = filtersState.isFeatureEnabled && !displayMigrationStatus val displayEmptyState = roomList is AsyncData.Success && roomList.data.isEmpty() sealed interface ContextMenu { diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 89597fea06..bfc155f2d2 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.aLeaveRoomState import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState +import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary import io.element.android.features.roomlist.impl.search.RoomListSearchState @@ -39,18 +41,19 @@ open class RoomListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomListState(), - aRoomListState(securityBannerState = SecurityBannerState.SessionVerification), aRoomListState(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)), aRoomListState(hasNetworkConnection = false), aRoomListState(invitesState = InvitesState.SeenInvites), aRoomListState(invitesState = InvitesState.NewInvites), aRoomListState(contextMenu = aContextMenuShown(roomName = "A nice room name")), aRoomListState(contextMenu = aContextMenuShown(isFavorite = true)), + aRoomListState(securityBannerState = SecurityBannerState.SessionVerification), aRoomListState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), aRoomListState(roomList = AsyncData.Success(persistentListOf())), aRoomListState(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())), aRoomListState(matrixUser = null, displayMigrationStatus = true), aRoomListState(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")), + aRoomListState(filtersState = aRoomListFiltersState(isFeatureEnabled = true)), ) } @@ -65,6 +68,7 @@ internal fun aRoomListState( contextMenu: RoomListState.ContextMenu = RoomListState.ContextMenu.Hidden, leaveRoomState: LeaveRoomState = aLeaveRoomState(), searchState: RoomListSearchState = aRoomListSearchState(), + filtersState: RoomListFiltersState = aRoomListFiltersState(isFeatureEnabled = false), displayMigrationStatus: Boolean = false, eventSink: (RoomListEvents) -> Unit = {} ) = RoomListState( @@ -78,6 +82,7 @@ internal fun aRoomListState( contextMenu = contextMenu, leaveRoomState = leaveRoomState, searchState = searchState, + filtersState = filtersState, displayMigrationStatus = displayMigrationStatus, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 290ea0f6a5..2415774260 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -55,6 +55,7 @@ import io.element.android.features.roomlist.impl.components.RequestVerificationH import io.element.android.features.roomlist.impl.components.RoomListMenuAction import io.element.android.features.roomlist.impl.components.RoomListTopBar import io.element.android.features.roomlist.impl.components.RoomSummaryRow +import io.element.android.features.roomlist.impl.filters.RoomListFiltersView import io.element.android.features.roomlist.impl.migration.MigrationScreenView import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.search.RoomListSearchView @@ -207,16 +208,21 @@ private fun RoomListContent( Scaffold( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - RoomListTopBar( - matrixUser = state.matrixUser, - showAvatarIndicator = state.showAvatarIndicator, - areSearchResultsDisplayed = state.searchState.isSearchActive, - onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) }, - onMenuActionClicked = onMenuActionClicked, - onOpenSettings = onOpenSettings, - scrollBehavior = scrollBehavior, - displayMenuItems = !state.displayMigrationStatus, - ) + Column { + RoomListTopBar( + matrixUser = state.matrixUser, + showAvatarIndicator = state.showAvatarIndicator, + areSearchResultsDisplayed = state.searchState.isSearchActive, + onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) }, + onMenuActionClicked = onMenuActionClicked, + onOpenSettings = onOpenSettings, + scrollBehavior = scrollBehavior, + displayMenuItems = !state.displayMigrationStatus, + ) + if (state.displayFilters) { + RoomListFiltersView(state = state.filtersState) + } + } }, content = { padding -> LazyColumn( @@ -272,7 +278,11 @@ private fun RoomListContent( } } if (state.displayEmptyState) { - EmptyRoomListView(onCreateRoomClicked) + if (state.filtersState.hasAnyFilterSelected) { + // TODO add empty state for filtered rooms + } else { + EmptyRoomListView(onCreateRoomClicked) + } } MigrationScreenView(isMigrating = state.displayMigrationStatus) }, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/di/RoomListModule.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/di/RoomListModule.kt index 369da2fc75..b66401695e 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/di/RoomListModule.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/di/RoomListModule.kt @@ -19,6 +19,8 @@ package io.element.android.features.roomlist.impl.di import com.squareup.anvil.annotations.ContributesTo import dagger.Binds import dagger.Module +import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter import io.element.android.features.roomlist.impl.search.RoomListSearchState import io.element.android.libraries.architecture.Presenter @@ -29,4 +31,7 @@ import io.element.android.libraries.di.SessionScope interface RoomListModule { @Binds fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter + + @Binds + fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt new file mode 100644 index 0000000000..31405c45e7 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt @@ -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 + +import io.element.android.features.roomlist.impl.R + +/** + * Enum class representing the different filters that can be applied to the room list. + * Order is important. + */ +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), + Favourites(R.string.screen_roomlist_filter_favourites); + + val oppositeFilter: RoomListFilter? + get() = when (this) { + Rooms -> People + People -> Rooms + Unread -> null + Favourites -> null + } +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEvents.kt new file mode 100644 index 0000000000..d243ea7ca0 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEvents.kt @@ -0,0 +1,22 @@ +/* + * 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 + +sealed interface RoomListFiltersEvents { + data object ClearSelectedFilters : RoomListFiltersEvents + data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt new file mode 100644 index 0000000000..56cc72e1a3 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt @@ -0,0 +1,98 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import kotlinx.collections.immutable.toPersistentList +import javax.inject.Inject +import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter + +class RoomListFiltersPresenter @Inject constructor( + private val roomListService: RoomListService, + private val featureFlagService: FeatureFlagService, +) : Presenter { + @Composable + override fun present(): RoomListFiltersState { + val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false) + var unselectedFilters: Set by rememberSaveable { + mutableStateOf(RoomListFilter.entries.toSet()) + } + var selectedFilters: Set by rememberSaveable { + mutableStateOf(emptySet()) + } + + fun updateFilters(newSelectedFilters: Set) { + selectedFilters = newSelectedFilters + unselectedFilters = RoomListFilter.entries.toSet() - + selectedFilters - + selectedFilters.mapNotNull { it.oppositeFilter }.toSet() + } + + fun handleEvents(event: RoomListFiltersEvents) { + when (event) { + is RoomListFiltersEvents.ToggleFilter -> { + val newSelectedFilters = if (selectedFilters.contains(event.filter)) { + selectedFilters - event.filter + } else { + selectedFilters + event.filter + } + updateFilters(newSelectedFilters) + } + RoomListFiltersEvents.ClearSelectedFilters -> { + updateFilters(newSelectedFilters = emptySet()) + } + } + } + + LaunchedEffect(isFeatureEnabled) { + if (!isFeatureEnabled) { + updateFilters(emptySet()) + } + } + + LaunchedEffect(selectedFilters) { + val allRoomsFilter = MatrixRoomListFilter.All( + selectedFilters.map { roomListFilter -> + when (roomListFilter) { + RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group + RoomListFilter.People -> MatrixRoomListFilter.Category.People + RoomListFilter.Unread -> MatrixRoomListFilter.Unread + RoomListFilter.Favourites -> MatrixRoomListFilter.Favorite + } + }.plus(MatrixRoomListFilter.NonLeft) + ) + roomListService.allRooms.updateFilter(allRoomsFilter) + } + + return RoomListFiltersState( + unselectedFilters = unselectedFilters.toPersistentList(), + selectedFilters = selectedFilters.toPersistentList(), + isFeatureEnabled = isFeatureEnabled, + eventSink = ::handleEvents + ) + } +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersState.kt new file mode 100644 index 0000000000..e496336742 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersState.kt @@ -0,0 +1,28 @@ +/* + * 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 + +import kotlinx.collections.immutable.ImmutableList + +data class RoomListFiltersState( + val unselectedFilters: ImmutableList, + val selectedFilters: ImmutableList, + val isFeatureEnabled: Boolean, + val eventSink: (RoomListFiltersEvents) -> Unit, +) { + val hasAnyFilterSelected = selectedFilters.isNotEmpty() +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt new file mode 100644 index 0000000000..281f014cc7 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt @@ -0,0 +1,45 @@ +/* + * 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 + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +class RoomListFiltersStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aRoomListFiltersState(), + aRoomListFiltersState( + selectedFilters = persistentListOf(RoomListFilter.Rooms, RoomListFilter.Favourites), + unselectedFilters = persistentListOf(RoomListFilter.Unread), + ), + ) +} + +fun aRoomListFiltersState( + unselectedFilters: ImmutableList = RoomListFilter.entries.toImmutableList(), + selectedFilters: ImmutableList = persistentListOf(), + isFeatureEnabled: Boolean = true, + eventSink: (RoomListFiltersEvents) -> Unit = {}, +) = RoomListFiltersState( + unselectedFilters = unselectedFilters, + selectedFilters = selectedFilters, + isFeatureEnabled = isFeatureEnabled, + eventSink = eventSink, +) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt new file mode 100644 index 0000000000..8aefe30d0c --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt @@ -0,0 +1,173 @@ +/* + * 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 + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +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.modifiers.fadingEdge +import io.element.android.libraries.designsystem.modifiers.horizontalFadingEdgesBrush +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun RoomListFiltersView( + state: RoomListFiltersState, + modifier: Modifier = Modifier +) { + fun onClearFiltersClicked() { + state.eventSink(RoomListFiltersEvents.ClearSelectedFilters) + } + + fun onFilterClicked(filter: RoomListFilter) { + state.eventSink(RoomListFiltersEvents.ToggleFilter(filter)) + } + + val startPadding = if (state.hasAnyFilterSelected) 4.dp else 16.dp + Row( + modifier = modifier.padding(start = startPadding, end = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + AnimatedVisibility(visible = state.hasAnyFilterSelected) { + RoomListClearFiltersButton( + modifier = Modifier.testTag(TestTags.homeScreenClearFilters), + onClick = ::onClearFiltersClicked + ) + } + val lazyListState = rememberLazyListState() + val fadingEdgesBrush = horizontalFadingEdgesBrush( + showLeft = lazyListState.canScrollBackward, + showRight = lazyListState.canScrollForward + ) + LazyRow( + modifier = Modifier + .fillMaxWidth() + .fadingEdge(fadingEdgesBrush), + state = lazyListState, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + roomListFilters(state.selectedFilters, selected = true, onClick = ::onFilterClicked) + roomListFilters(state.unselectedFilters, selected = false, onClick = ::onFilterClicked) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +private fun LazyListScope.roomListFilters( + filters: ImmutableList, + selected: Boolean, + onClick: (RoomListFilter) -> Unit, +) { + items( + items = filters, + key = { it.ordinal }, + ) { filter -> + RoomListFilterView( + modifier = Modifier.animateItemPlacement(), + roomListFilter = filter, + selected = selected, + onClick = onClick, + ) + } +} + +@Composable +private fun RoomListClearFiltersButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Box( + modifier = Modifier + .clip(CircleShape) + .background(ElementTheme.colors.bgActionPrimaryRest) + ) { + Icon( + modifier = Modifier.align(Alignment.Center), + imageVector = CompoundIcons.Close(), + tint = ElementTheme.colors.iconOnSolidPrimary, + contentDescription = stringResource(id = io.element.android.libraries.ui.strings.R.string.action_clear), + ) + } + } +} + +@Composable +private fun RoomListFilterView( + roomListFilter: RoomListFilter, + selected: Boolean, + onClick: (RoomListFilter) -> Unit, + modifier: Modifier = Modifier +) { + FilterChip( + selected = selected, + onClick = { onClick(roomListFilter) }, + modifier = modifier + .minimumInteractiveComponentSize() + .height(36.dp), + shape = CircleShape, + colors = FilterChipDefaults.filterChipColors( + containerColor = ElementTheme.colors.bgCanvasDefault, + selectedContainerColor = ElementTheme.colors.bgActionPrimaryRest, + labelColor = ElementTheme.colors.textPrimary, + selectedLabelColor = ElementTheme.colors.textOnSolidPrimary, + ), + label = { + Text(text = stringResource(id = roomListFilter.stringResource)) + } + ) +} + +@PreviewsDayNight +@Composable +internal fun RoomListFiltersViewPreview(@PreviewParameter(RoomListFiltersStateProvider::class) state: RoomListFiltersState) = ElementPreview { + RoomListFiltersView( + state = state, + ) +} diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 7fe08975e1..4e83e681fd 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -31,6 +31,8 @@ import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState +import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary @@ -612,6 +614,7 @@ class RoomListPresenterTests { migrationScreenStore = InMemoryMigrationScreenStore(), ), analyticsService: AnalyticsService = FakeAnalyticsService(), + filtersPresenter: Presenter = Presenter { aRoomListFiltersState() }, searchPresenter: Presenter = Presenter { aRoomListSearchState() }, ) = RoomListPresenter( client = client, @@ -637,6 +640,7 @@ class RoomListPresenterTests { migrationScreenPresenter = migrationScreenPresenter, searchPresenter = searchPresenter, sessionPreferencesStore = sessionPreferencesStore, + filtersPresenter = filtersPresenter, analyticsService = analyticsService, ) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenterTests.kt new file mode 100644 index 0000000000..995c02762a --- /dev/null +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenterTests.kt @@ -0,0 +1,125 @@ +/* + * 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 + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.tests.testutils.awaitLastSequentialItem +import kotlinx.coroutines.test.runTest +import org.junit.Test +import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter + +class RoomListFiltersPresenterTests { + @Test + fun `present - initial state`() = runTest { + val presenter = createRoomListFiltersPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().let { state -> + assertThat(state.selectedFilters).isEmpty() + assertThat(state.hasAnyFilterSelected).isFalse() + assertThat(state.unselectedFilters).containsExactly( + RoomListFilter.Rooms, + RoomListFilter.People, + RoomListFilter.Unread, + RoomListFilter.Favourites, + ) + } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - toggle rooms filter`() = runTest { + val roomListService = FakeRoomListService() + val presenter = createRoomListFiltersPresenter(roomListService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) + + awaitLastSequentialItem().let { state -> + + assertThat(state.selectedFilters).containsExactly(RoomListFilter.Rooms) + assertThat(state.hasAnyFilterSelected).isTrue() + assertThat(state.unselectedFilters).containsExactly( + RoomListFilter.Unread, + RoomListFilter.Favourites, + ) + val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All + assertThat(roomListCurrentFilter.filters).containsExactly( + MatrixRoomListFilter.NonLeft, + MatrixRoomListFilter.Category.Group, + ) + + state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) + } + + awaitLastSequentialItem().let { state -> + assertThat(state.selectedFilters).isEmpty() + assertThat(state.hasAnyFilterSelected).isFalse() + assertThat(state.unselectedFilters).containsExactly( + RoomListFilter.Rooms, + RoomListFilter.People, + RoomListFilter.Unread, + RoomListFilter.Favourites, + ) + val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All + assertThat(roomListCurrentFilter.filters).containsExactly( + MatrixRoomListFilter.NonLeft, + ) + } + } + } + + @Test + fun `present - clear filters event`() = runTest { + val roomListService = FakeRoomListService() + val presenter = createRoomListFiltersPresenter(roomListService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) + awaitLastSequentialItem().let { state -> + assertThat(state.selectedFilters).isNotEmpty() + assertThat(state.hasAnyFilterSelected).isTrue() + state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters) + } + awaitLastSequentialItem().let { state -> + assertThat(state.selectedFilters).isEmpty() + assertThat(state.hasAnyFilterSelected).isFalse() + } + } + } +} + +fun createRoomListFiltersPresenter( + roomListService: RoomListService = FakeRoomListService(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), +): RoomListFiltersPresenter { + return RoomListFiltersPresenter( + roomListService = roomListService, + featureFlagService = featureFlagService, + ) +} diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersViewTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersViewTests.kt new file mode 100644 index 0000000000..6c9bd9e050 --- /dev/null +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersViewTests.kt @@ -0,0 +1,72 @@ +/* + * 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 + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.roomlist.impl.R +import io.element.android.libraries.testtags.TestTags +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.pressTag +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RoomListFiltersViewTests { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on filters generates expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + RoomListFiltersView( + state = aRoomListFiltersState(eventSink = eventsRecorder), + ) + } + rule.clickOn(R.string.screen_roomlist_filter_rooms) + eventsRecorder.assertList( + listOf( + RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms), + ) + ) + } + + @Test + fun `clicking on clear filters generates expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + RoomListFiltersView( + state = aRoomListFiltersState( + unselectedFilters = persistentListOf(), + selectedFilters = RoomListFilter.entries.toImmutableList(), + eventSink = eventsRecorder + ), + ) + } + rule.pressTag(TestTags.homeScreenClearFilters.value) + eventsRecorder.assertList( + listOf( + RoomListFiltersEvents.ClearSelectedFilters, + ) + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt new file mode 100644 index 0000000000..734e2181a3 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt @@ -0,0 +1,57 @@ +/* + * 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.libraries.designsystem.modifiers + +import androidx.compose.animation.animateColorAsState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer + +@Composable +fun horizontalFadingEdgesBrush( + showLeft: Boolean, + showRight: Boolean, + percent: Float = 0.1f, +): Brush { + val leftColor by animateColorAsState( + targetValue = if (showLeft) Color.Transparent else Color.White, + label = "AnimateLeftColor", + ) + val rightColor by animateColorAsState( + targetValue = if (showRight) Color.Transparent else Color.White, + label = "AnimateRightColor", + ) + return Brush.horizontalGradient( + 0f to leftColor, + percent to Color.White, + 1f - percent to Color.White, + 1f to rightColor + ) +} + +fun Modifier.fadingEdge(brush: Brush) = this + .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) + .drawWithContent { + drawContent() + drawRect(brush = brush, blendMode = BlendMode.DstIn) + } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index fea9088c2a..82c4375872 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -75,4 +75,11 @@ enum class FeatureFlags( defaultValue = true, isFinished = false, ), + RoomListFilters( + key = "feature.roomlistfilters", + title = "Room list filters", + description = "Allow user to filter the room list", + defaultValue = true, + isFinished = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 757f01904f..c5f868ccc3 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -40,6 +40,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.PinUnlock -> true FeatureFlags.Mentions -> true FeatureFlags.MarkAsUnread -> false + FeatureFlags.RoomListFilters -> false } } else { false diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt index 41f5240a19..4ea79f31bb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt @@ -57,6 +57,11 @@ sealed interface RoomListFilter { */ data object Unread : RoomListFilter + /** + * A filter that matches rooms that are marked as favorite. + */ + data object Favorite : RoomListFilter + /** * A filter that matches either Group or People rooms. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt index c28da59ea4..d72ff9fbeb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt @@ -31,5 +31,6 @@ fun RoomListFilter.toRustFilter(): RoomListEntriesDynamicFilterKind { RoomListFilter.None -> RoomListEntriesDynamicFilterKind.None is RoomListFilter.NormalizedMatchRoomName -> RoomListEntriesDynamicFilterKind.NormalizedMatchRoomName(pattern) RoomListFilter.Unread -> RoomListEntriesDynamicFilterKind.Unread + RoomListFilter.Favorite -> RoomListEntriesDynamicFilterKind.Favourite } } diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index f8ff10b979..1d237979e6 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -52,6 +52,7 @@ object TestTags { * Room list / Home screen. */ val homeScreenSettings = TestTag("home_screen-settings") + val homeScreenClearFilters = TestTag("home_screen-clear_filters") /** * Room detail screen. diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index a7d38a2036..40245d2701 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -28,6 +28,7 @@ import io.element.android.features.roomlist.impl.RoomListView import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory +import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter import io.element.android.features.roomlist.impl.migration.SharedPrefsMigrationScreenStore import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter @@ -120,6 +121,10 @@ class RoomListScreen( sessionId = matrixClient.sessionId, sessionCoroutineScope = Singleton.appScope ), + filtersPresenter = RoomListFiltersPresenter( + roomListService = matrixClient.roomListService, + featureFlagService = featureFlagService, + ), analyticsService = NoopAnalyticsService(), ) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4df8ca69f4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bb5af2c6ad9a295f48dd1e7dbb0df838e55c294ba941658a34356bf1066bf88 +size 15056 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..38895fcaba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:034ddb5e1a44b27f86f115741540e826b636d0130f44ea5afea41ada0cd42ea1 +size 14141 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e8cdd870e8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8022de099e17b4e247f7d7f009425afeea7dc9ff3d8381cf0462392259e04968 +size 14943 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8e5c5d0bd1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46aa240613c675c37f4152679c5103bdfc538238acc723952e311cd3a08666ae +size 13794 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-10_11_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-10_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-10_12_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png index 76d8712c46..85b6547bf4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f3a616a5cb071f47ec9e4f25febee92b75c93159d2711cdb09620c2bdd9faac -size 86579 +oid sha256:ae4d11b9817587809471efeaff6ea4caee21368454bf6d406817c7f512f60a96 +size 65053 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..61a7df8ed3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed25d63e6e6a8b46285e8c42ae566e31f71d44ce592beb251bf603ab78751832 +size 74701 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png index 85b6547bf4..94174cabbd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae4d11b9817587809471efeaff6ea4caee21368454bf6d406817c7f512f60a96 -size 65053 +oid sha256:f8dbd008a1fe52fe384c926fc8f6d80e330c372531266ddb44d92e4ae0196d6b +size 65039 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png index 94174cabbd..1f7a64dd57 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8dbd008a1fe52fe384c926fc8f6d80e330c372531266ddb44d92e4ae0196d6b -size 65039 +oid sha256:f1b059ad559e3d3408f0a4526f6267612b84463e9564d6faae386171784bfb0f +size 66105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png index 1f7a64dd57..b9f5e64779 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1b059ad559e3d3408f0a4526f6267612b84463e9564d6faae386171784bfb0f -size 66105 +oid sha256:26605a6ae4ebd7027f8582d08baf5a4f3e37f19aff484605d33b5688bfec3489 +size 66481 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png index b9f5e64779..2f2ac0e0c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26605a6ae4ebd7027f8582d08baf5a4f3e37f19aff484605d33b5688bfec3489 -size 66481 +oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 +size 4462 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png index 2f2ac0e0c0..76d8712c46 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 -size 4462 +oid sha256:7f3a616a5cb071f47ec9e4f25febee92b75c93159d2711cdb09620c2bdd9faac +size 86579 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png index 3f5a09b743..746061da59 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8a0661d4cf43cdd43c052fabe5f00d10c7f3eb73ecfbc382cff2b4fbce2777b -size 88503 +oid sha256:dfb268513ed16447ced5f73eb0a10ebbefdea39a3a6a482c30178fb4196dbed5 +size 67314 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3c911ed18b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:354c5b37f5cd8b91f11c6c1dd72a460463cd8d46f11b7da069fd7745bf822d1f +size 77215 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png index 746061da59..1d607a1e4a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfb268513ed16447ced5f73eb0a10ebbefdea39a3a6a482c30178fb4196dbed5 -size 67314 +oid sha256:b161a9b27b08df85a5bd02aef4857940d60cfdaa6c8e7416517d97f267b14ee2 +size 67017 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png index 1d607a1e4a..6fcda0fcc3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b161a9b27b08df85a5bd02aef4857940d60cfdaa6c8e7416517d97f267b14ee2 -size 67017 +oid sha256:299bcbf0d993256e62c2f96ab2b14d563f82800a8f8157693130e7426d0ea914 +size 68871 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png index 6fcda0fcc3..8696da8528 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:299bcbf0d993256e62c2f96ab2b14d563f82800a8f8157693130e7426d0ea914 -size 68871 +oid sha256:750f794e32c90681defc871e83a3990ecdc2e7321cce9b503a9d95db191a66ee +size 69236 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png index 8696da8528..2f2ac0e0c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:750f794e32c90681defc871e83a3990ecdc2e7321cce9b503a9d95db191a66ee -size 69236 +oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 +size 4462 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png index 2f2ac0e0c0..3f5a09b743 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 -size 4462 +oid sha256:f8a0661d4cf43cdd43c052fabe5f00d10c7f3eb73ecfbc382cff2b4fbce2777b +size 88503