From 7471e12bc5236a8a7bc512c173ba5a1ff8afb051 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 20 Feb 2024 21:17:16 +0100 Subject: [PATCH] RoomListFilters: try to improve ui with animation and fading edges --- .../roomlist/impl/RoomListPresenter.kt | 1 - .../roomlist/impl/filters/RoomListFilter.kt | 2 +- .../impl/filters/RoomListFiltersPresenter.kt | 2 - .../filters/RoomListFiltersStateProvider.kt | 1 - .../impl/filters/RoomListFiltersView.kt | 65 +++++++++++++------ .../roomlist/impl/RoomListPresenterTests.kt | 1 - .../filters/RoomListFiltersPresenterTests.kt | 1 - .../designsystem/modifiers/FadingEdge.kt | 57 ++++++++++++++++ 8 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt 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 f901502b79..a1327d384d 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 @@ -36,7 +36,6 @@ 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.RoomListFiltersPresenter 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 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 index 4ebe9f10df..31405c45e7 100644 --- 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 @@ -22,7 +22,7 @@ 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){ +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), 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 index 28f4ad5879..297f4ffdbf 100644 --- 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 @@ -35,7 +35,6 @@ 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) @@ -91,4 +90,3 @@ class RoomListFiltersPresenter @Inject constructor( ) } } - 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 index 4c9f5dcc0c..376b879d50 100644 --- 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 @@ -22,7 +22,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList class RoomListFiltersStateProvider : PreviewParameterProvider { - override val values: Sequence get() = sequenceOf( aRoomListFiltersState(), 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 index 6876e0661f..da827a6351 100644 --- 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 @@ -17,13 +17,17 @@ 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.horizontalScroll 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.padding -import androidx.compose.foundation.rememberScrollState +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 @@ -36,11 +40,14 @@ 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 kotlinx.collections.immutable.ImmutableList import timber.log.Timber @Composable @@ -61,34 +68,50 @@ fun RoomListFiltersView( state.eventSink(RoomListFiltersEvents.ToggleFilter(filter)) } - val horizontalPadding = if (state.showClearFilterButton) 4.dp else 16.dp - val scrollState = rememberScrollState() + val startPadding = if (state.showClearFilterButton) 4.dp else 16.dp Row( - modifier = modifier - .padding(horizontal = horizontalPadding) - .horizontalScroll(scrollState), - horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier.padding(start = startPadding, end = 16.dp), ) { AnimatedVisibility(visible = state.showClearFilterButton) { RoomListClearFiltersButton(onClick = ::onClearFiltersClicked) } - for (filter in state.selectedFilters) { - RoomListFilterView( - roomListFilter = filter, - selected = true, - onClick = ::onFilterClicked, - ) - } - for (filter in state.unselectedFilters) { - RoomListFilterView( - roomListFilter = filter, - selected = false, - onClick = ::onFilterClicked, - ) + 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, 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 e8d812c1b9..f0886ca98d 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 @@ -30,7 +30,6 @@ 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.RoomListFiltersPresenter 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 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 index f161dd8d5f..8bdb10c4b2 100644 --- 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 @@ -30,7 +30,6 @@ 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() 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) + }