From 4adbf5e2974e593ebb403220f83fed53d8d79cea Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 11 Mar 2024 18:59:59 +0100 Subject: [PATCH] RoomList : add empty state when filtering --- .../features/roomlist/impl/RoomListView.kt | 1 + .../impl/components/RoomListContentView.kt | 108 +++++++++++++----- .../RoomListFiltersEmptyStateResources.kt | 66 +++++++++++ .../impl/filters/RoomListFiltersState.kt | 4 + 4 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEmptyStateResources.kt 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 08accccc67..f8cb6f137b 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 @@ -180,6 +180,7 @@ private fun RoomListScaffold( content = { padding -> RoomListContentView( contentState = state.contentState, + filtersState = state.filtersState, eventSink = state.eventSink, onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index b58b8ebeb1..612264bb0c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -16,9 +16,13 @@ package io.element.android.features.roomlist.impl.components +import androidx.annotation.StringRes +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height @@ -47,6 +51,9 @@ import io.element.android.features.roomlist.impl.RoomListContentState import io.element.android.features.roomlist.impl.RoomListEvents import io.element.android.features.roomlist.impl.SecurityBannerState import io.element.android.features.roomlist.impl.contentType +import io.element.android.features.roomlist.impl.filters.RoomListFilter +import io.element.android.features.roomlist.impl.filters.RoomListFiltersEmptyStateResources +import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.migration.MigrationScreenView import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.designsystem.theme.components.Button @@ -58,6 +65,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun RoomListContentView( contentState: RoomListContentState, + filtersState: RoomListFiltersState, eventSink: (RoomListEvents) -> Unit, onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, @@ -87,6 +95,7 @@ fun RoomListContentView( is RoomListContentState.Rooms -> { RoomsView( state = contentState, + filtersState = filtersState, eventSink = eventSink, onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, @@ -127,22 +136,8 @@ private fun EmptyView( InvitesEntryPointView(onInvitesClicked, state.invitesState) } EmptyScaffold( - title = { - Text( - text = stringResource(R.string.screen_roomlist_empty_title), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - }, - subtitle = { - Text( - text = stringResource(R.string.screen_roomlist_empty_message), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - }, + title = R.string.screen_roomlist_empty_title, + subtitle = R.string.screen_roomlist_empty_message, action = { Button( text = stringResource(CommonStrings.action_start_chat), @@ -150,13 +145,44 @@ private fun EmptyView( onClick = onCreateRoomClicked, ) }, - modifier = Modifier, + modifier = Modifier.fillMaxSize(), ) } } @Composable private fun RoomsView( + state: RoomListContentState.Rooms, + filtersState: RoomListFiltersState, + eventSink: (RoomListEvents) -> Unit, + onVerifyClicked: () -> Unit, + onConfirmRecoveryKeyClicked: () -> Unit, + onRoomClicked: (RoomListRoomSummary) -> Unit, + onRoomLongClicked: (RoomListRoomSummary) -> Unit, + onInvitesClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) { + EmptyViewForFilterStates( + selectedFilters = filtersState.selectedFilters(), + modifier = modifier.fillMaxSize() + ) + } else { + RoomsViewList( + state = state, + eventSink = eventSink, + onVerifyClicked = onVerifyClicked, + onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, + onRoomClicked = onRoomClicked, + onRoomLongClicked = onRoomLongClicked, + onInvitesClicked = onInvitesClicked, + modifier = modifier.fillMaxSize(), + ) + } +} + +@Composable +private fun RoomsViewList( state: RoomListContentState.Rooms, eventSink: (RoomListEvents) -> Unit, onVerifyClicked: () -> Unit, @@ -186,7 +212,9 @@ private fun RoomsView( LazyColumn( state = lazyListState, modifier = modifier.nestedScroll(nestedScrollConnection), - verticalArrangement = Arrangement.spacedBy(8.dp) + // FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80 + contentPadding = PaddingValues(bottom = 80.dp) + ) { when (state.securityBannerState) { SecurityBannerState.SessionVerification -> { @@ -232,24 +260,44 @@ private fun RoomsView( } @Composable -private fun EmptyScaffold( - title: @Composable () -> Unit, +private fun EmptyViewForFilterStates( + selectedFilters: List, modifier: Modifier = Modifier, - subtitle: @Composable (() -> Unit)? = null, - action: @Composable (() -> Unit)? = null, +) { + val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) ?: return + EmptyScaffold( + title = emptyStateResources.title, + subtitle = emptyStateResources.subtitle, + modifier = modifier, + ) +} + +@Composable +private fun EmptyScaffold( + @StringRes title: Int, + @StringRes subtitle: Int, + modifier: Modifier = Modifier, + action: @Composable (ColumnScope.() -> Unit)? = null, ) { Column( - modifier = modifier - .fillMaxSize() - .padding(horizontal = 60.dp), + modifier = modifier.padding(horizontal = 60.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - title() - Spacer(modifier = Modifier.height(4.dp)) - subtitle?.invoke() + Text( + text = stringResource(title), + style = ElementTheme.typography.fontHeadingMdBold, + color = ElementTheme.colors.textPrimary, + textAlign = TextAlign.Center, + ) Spacer(modifier = Modifier.height(16.dp)) - action?.invoke() + Text( + text = stringResource(subtitle), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(32.dp)) + action?.invoke(this) } } - diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEmptyStateResources.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEmptyStateResources.kt new file mode 100644 index 0000000000..f0f29b0db6 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEmptyStateResources.kt @@ -0,0 +1,66 @@ +/* + * 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.annotation.StringRes +import io.element.android.features.roomlist.impl.R + +/** + * Holds the resources for the empty state when filters are applied to the room list. + * @param title the title of the empty state + * @param subtitle the subtitle of the empty state + */ +data class RoomListFiltersEmptyStateResources( + @StringRes val title: Int, + @StringRes val subtitle: Int, +) { + + companion object { + /** + * Create a [RoomListFiltersEmptyStateResources] from a list of selected filters. + */ + fun fromSelectedFilters(selectedFilters: List): RoomListFiltersEmptyStateResources? { + return when { + selectedFilters.isEmpty() -> null + selectedFilters.size == 1 -> { + when (selectedFilters.first()) { + RoomListFilter.Unread -> RoomListFiltersEmptyStateResources( + title = R.string.screen_roomlist_filter_unreads_empty_state_title, + subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle + ) + RoomListFilter.People -> RoomListFiltersEmptyStateResources( + title = R.string.screen_roomlist_filter_people_empty_state_title, + subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle + ) + RoomListFilter.Rooms -> RoomListFiltersEmptyStateResources( + title = R.string.screen_roomlist_filter_rooms_empty_state_title, + subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle + ) + RoomListFilter.Favourites -> RoomListFiltersEmptyStateResources( + title = R.string.screen_roomlist_filter_favourites_empty_state_title, + subtitle = R.string.screen_roomlist_filter_favourites_empty_state_subtitle + ) + } + } + else -> RoomListFiltersEmptyStateResources( + title = R.string.screen_roomlist_filter_mixed_empty_state_title, + subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle + ) + } + } + } +} 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 index f75fbf059f..7fce841aaf 100644 --- 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 @@ -25,4 +25,8 @@ data class RoomListFiltersState( val eventSink: (RoomListFiltersEvents) -> Unit, ) { val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected } + + fun selectedFilters(): List { + return filterSelectionStates.filter { it.isSelected }.map { it.filter } + } }