RoomList : add empty state when filtering

This commit is contained in:
ganfra 2024-03-11 18:59:59 +01:00
parent c03dc07dcf
commit fbbde977f9
4 changed files with 149 additions and 30 deletions

View file

@ -180,6 +180,7 @@ private fun RoomListScaffold(
content = { padding -> content = { padding ->
RoomListContentView( RoomListContentView(
contentState = state.contentState, contentState = state.contentState,
filtersState = state.filtersState,
eventSink = state.eventSink, eventSink = state.eventSink,
onVerifyClicked = onVerifyClicked, onVerifyClicked = onVerifyClicked,
onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked,

View file

@ -16,9 +16,13 @@
package io.element.android.features.roomlist.impl.components 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height 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.RoomListEvents
import io.element.android.features.roomlist.impl.SecurityBannerState import io.element.android.features.roomlist.impl.SecurityBannerState
import io.element.android.features.roomlist.impl.contentType 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.migration.MigrationScreenView
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Button
@ -58,6 +65,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RoomListContentView( fun RoomListContentView(
contentState: RoomListContentState, contentState: RoomListContentState,
filtersState: RoomListFiltersState,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvents) -> Unit,
onVerifyClicked: () -> Unit, onVerifyClicked: () -> Unit,
onConfirmRecoveryKeyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit,
@ -87,6 +95,7 @@ fun RoomListContentView(
is RoomListContentState.Rooms -> { is RoomListContentState.Rooms -> {
RoomsView( RoomsView(
state = contentState, state = contentState,
filtersState = filtersState,
eventSink = eventSink, eventSink = eventSink,
onVerifyClicked = onVerifyClicked, onVerifyClicked = onVerifyClicked,
onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked,
@ -127,22 +136,8 @@ private fun EmptyView(
InvitesEntryPointView(onInvitesClicked, state.invitesState) InvitesEntryPointView(onInvitesClicked, state.invitesState)
} }
EmptyScaffold( EmptyScaffold(
title = { title = R.string.screen_roomlist_empty_title,
Text( subtitle = R.string.screen_roomlist_empty_message,
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,
)
},
action = { action = {
Button( Button(
text = stringResource(CommonStrings.action_start_chat), text = stringResource(CommonStrings.action_start_chat),
@ -150,13 +145,44 @@ private fun EmptyView(
onClick = onCreateRoomClicked, onClick = onCreateRoomClicked,
) )
}, },
modifier = Modifier, modifier = Modifier.fillMaxSize(),
) )
} }
} }
@Composable @Composable
private fun RoomsView( 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, state: RoomListContentState.Rooms,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvents) -> Unit,
onVerifyClicked: () -> Unit, onVerifyClicked: () -> Unit,
@ -186,7 +212,9 @@ private fun RoomsView(
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
modifier = modifier.nestedScroll(nestedScrollConnection), 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) { when (state.securityBannerState) {
SecurityBannerState.SessionVerification -> { SecurityBannerState.SessionVerification -> {
@ -232,24 +260,44 @@ private fun RoomsView(
} }
@Composable @Composable
private fun EmptyScaffold( private fun EmptyViewForFilterStates(
title: @Composable () -> Unit, selectedFilters: List<RoomListFilter>,
modifier: Modifier = Modifier, 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( Column(
modifier = modifier modifier = modifier.padding(horizontal = 60.dp),
.fillMaxSize()
.padding(horizontal = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
title() Text(
Spacer(modifier = Modifier.height(4.dp)) text = stringResource(title),
subtitle?.invoke() style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(16.dp)) 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)
} }
} }

View file

@ -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<RoomListFilter>): 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
)
}
}
}
}

View file

@ -25,4 +25,8 @@ data class RoomListFiltersState(
val eventSink: (RoomListFiltersEvents) -> Unit, val eventSink: (RoomListFiltersEvents) -> Unit,
) { ) {
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected } val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }
fun selectedFilters(): List<RoomListFilter> {
return filterSelectionStates.filter { it.isSelected }.map { it.filter }
}
} }