Browse Source

RoomListFilters: try to improve ui with animation and fading edges

jme/room_list_filters_embedded
ganfra 7 months ago
parent
commit
7471e12bc5
  1. 1
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  2. 2
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt
  3. 2
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt
  4. 1
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt
  5. 65
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt
  6. 1
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  7. 1
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenterTests.kt
  8. 57
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt

1
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.preferences.api.store.SessionPreferencesStore
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource 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.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.filters.RoomListFiltersState
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter 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.RoomListSearchEvents

2
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. * Enum class representing the different filters that can be applied to the room list.
* Order is important. * Order is important.
*/ */
enum class RoomListFilter(val stringResource: Int){ enum class RoomListFilter(val stringResource: Int) {
Rooms(R.string.screen_roomlist_filter_rooms), Rooms(R.string.screen_roomlist_filter_rooms),
People(R.string.screen_roomlist_filter_people), People(R.string.screen_roomlist_filter_people),
Unread(R.string.screen_roomlist_filter_unreads), Unread(R.string.screen_roomlist_filter_unreads),

2
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 roomListService: RoomListService,
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
) : Presenter<RoomListFiltersState> { ) : Presenter<RoomListFiltersState> {
@Composable @Composable
override fun present(): RoomListFiltersState { override fun present(): RoomListFiltersState {
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false) val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false)
@ -91,4 +90,3 @@ class RoomListFiltersPresenter @Inject constructor(
) )
} }
} }

1
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 import kotlinx.collections.immutable.toImmutableList
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> { class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> {
override val values: Sequence<RoomListFiltersState> override val values: Sequence<RoomListFiltersState>
get() = sequenceOf( get() = sequenceOf(
aRoomListFiltersState(), aRoomListFiltersState(),

65
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 package io.element.android.features.roomlist.impl.filters
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.foundation.shape.CircleShape
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.FilterChipDefaults
@ -36,11 +40,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons 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.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon 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.IconButton
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import kotlinx.collections.immutable.ImmutableList
import timber.log.Timber import timber.log.Timber
@Composable @Composable
@ -61,34 +68,50 @@ fun RoomListFiltersView(
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter)) state.eventSink(RoomListFiltersEvents.ToggleFilter(filter))
} }
val horizontalPadding = if (state.showClearFilterButton) 4.dp else 16.dp val startPadding = if (state.showClearFilterButton) 4.dp else 16.dp
val scrollState = rememberScrollState()
Row( Row(
modifier = modifier modifier = modifier.padding(start = startPadding, end = 16.dp),
.padding(horizontal = horizontalPadding)
.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
AnimatedVisibility(visible = state.showClearFilterButton) { AnimatedVisibility(visible = state.showClearFilterButton) {
RoomListClearFiltersButton(onClick = ::onClearFiltersClicked) RoomListClearFiltersButton(onClick = ::onClearFiltersClicked)
} }
for (filter in state.selectedFilters) { val lazyListState = rememberLazyListState()
RoomListFilterView( val fadingEdgesBrush = horizontalFadingEdgesBrush(
roomListFilter = filter, showLeft = lazyListState.canScrollBackward,
selected = true, showRight = lazyListState.canScrollForward
onClick = ::onFilterClicked, )
) LazyRow(
} modifier = Modifier
for (filter in state.unselectedFilters) { .fillMaxWidth()
RoomListFilterView( .fadingEdge(fadingEdgesBrush),
roomListFilter = filter, state = lazyListState,
selected = false, horizontalArrangement = Arrangement.spacedBy(8.dp)
onClick = ::onFilterClicked, ) {
) roomListFilters(state.selectedFilters, selected = true, onClick = ::onFilterClicked)
roomListFilters(state.unselectedFilters, selected = false, onClick = ::onFilterClicked)
} }
} }
} }
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.roomListFilters(
filters: ImmutableList<RoomListFilter>,
selected: Boolean,
onClick: (RoomListFilter) -> Unit,
) {
items(
items = filters,
key = { it.ordinal },
) { filter ->
RoomListFilterView(
modifier = Modifier.animateItemPlacement(),
roomListFilter = filter,
selected = selected,
onClick = onClick,
)
}
}
@Composable @Composable
private fun RoomListClearFiltersButton( private fun RoomListClearFiltersButton(
onClick: () -> Unit, onClick: () -> Unit,

1
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.InviteStateDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource 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.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.RoomListFiltersState
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState 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.InMemoryMigrationScreenStore

1
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 import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
class RoomListFiltersPresenterTests { class RoomListFiltersPresenterTests {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createRoomListFiltersPresenter() val presenter = createRoomListFiltersPresenter()

57
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)
}
Loading…
Cancel
Save