RoomList : add empty state when filtering
This commit is contained in:
parent
c03dc07dcf
commit
fbbde977f9
4 changed files with 149 additions and 30 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue