Add empty state view for HomeSpacesView (#6047)
* Add empty state view for `HomeSpacesView` This links to the create space flow, and has an 'explore public spaces', hidden for now. * Make sure we display the empty view if the 'create spaces' FF is enabled Also, remove the tab and navigate to the chats tab if the FF is disabled and the last space is left * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
1cbf7d9624
commit
941340f250
@@ -94,9 +94,9 @@ class HomePresenter(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(homeSpacesState.spaceRooms.isEmpty()) {
|
||||
// If the last space is left, ensure that the Chat view is rendered.
|
||||
if (homeSpacesState.spaceRooms.isEmpty()) {
|
||||
LaunchedEffect(homeSpacesState.canCreateSpaces, homeSpacesState.spaceRooms.isEmpty()) {
|
||||
// If the flag to create spaces is disabled and the last space is left, ensure that the Chat view is rendered.
|
||||
if (!homeSpacesState.canCreateSpaces && homeSpacesState.spaceRooms.isEmpty()) {
|
||||
currentHomeNavigationBarItemOrdinal = HomeNavigationBarItem.Chats.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@ data class HomeState(
|
||||
) {
|
||||
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
||||
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
||||
val showNavigationBar = homeSpacesState.spaceRooms.isNotEmpty()
|
||||
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
||||
}
|
||||
|
||||
@@ -268,7 +268,10 @@ private fun HomeScaffold(
|
||||
lazyListState = spacesLazyListState,
|
||||
onSpaceClick = { spaceId ->
|
||||
onRoomClick(spaceId)
|
||||
}
|
||||
},
|
||||
onCreateSpaceClick = onCreateSpaceClick,
|
||||
// TODO use actual callbacks for this
|
||||
onExploreClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ class HomeSpacesPresenter(
|
||||
seenSpaceInvites = seenSpaceInvites,
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
canCreateSpaces = canCreateSpaces,
|
||||
// TODO enable once we can link to the screen to explore public spaces
|
||||
canExploreSpaces = false,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ data class HomeSpacesState(
|
||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||
val hideInvitesAvatar: Boolean,
|
||||
val canCreateSpaces: Boolean,
|
||||
val canExploreSpaces: Boolean,
|
||||
val eventSink: (HomeSpacesEvents) -> Unit,
|
||||
)
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||
spaceRooms = aListOfSpaceRooms(),
|
||||
canCreateSpaces = false,
|
||||
),
|
||||
aHomeSpacesState(
|
||||
space = CurrentSpace.Root,
|
||||
spaceRooms = emptyList(),
|
||||
canCreateSpaces = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,6 +51,7 @@ internal fun aHomeSpacesState(
|
||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||
hideInvitesAvatar: Boolean = false,
|
||||
canCreateSpaces: Boolean = true,
|
||||
canExploreSpaces: Boolean = true,
|
||||
eventSink: (HomeSpacesEvents) -> Unit = {},
|
||||
) = HomeSpacesState(
|
||||
space = space,
|
||||
@@ -53,6 +59,7 @@ internal fun aHomeSpacesState(
|
||||
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
canCreateSpaces = canCreateSpaces,
|
||||
canExploreSpaces = canExploreSpaces,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,23 +8,40 @@
|
||||
|
||||
package io.element.android.features.home.impl.spaces
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderView
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
@@ -32,56 +49,119 @@ fun HomeSpacesView(
|
||||
state: HomeSpacesState,
|
||||
lazyListState: LazyListState,
|
||||
onSpaceClick: (RoomId) -> Unit,
|
||||
onCreateSpaceClick: () -> Unit,
|
||||
onExploreClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = lazyListState
|
||||
) {
|
||||
val space = state.space
|
||||
when (space) {
|
||||
CurrentSpace.Root -> {
|
||||
item {
|
||||
SpaceHeaderRootView(numberOfSpaces = state.spaceRooms.size)
|
||||
if (state.canCreateSpaces && state.spaceRooms.isEmpty()) {
|
||||
EmptySpaceHomeView(
|
||||
modifier = modifier,
|
||||
onCreateSpaceClick = onCreateSpaceClick,
|
||||
onExploreClick = onExploreClick,
|
||||
canExploreSpaces = state.canExploreSpaces,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = lazyListState
|
||||
) {
|
||||
val space = state.space
|
||||
when (space) {
|
||||
CurrentSpace.Root -> {
|
||||
item {
|
||||
SpaceHeaderRootView(numberOfSpaces = state.spaceRooms.size)
|
||||
}
|
||||
}
|
||||
is CurrentSpace.Space -> {
|
||||
item {
|
||||
SpaceHeaderView(
|
||||
avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader),
|
||||
name = space.spaceRoom.displayName,
|
||||
topic = space.spaceRoom.topic,
|
||||
visibility = space.spaceRoom.visibility,
|
||||
heroes = space.spaceRoom.heroes.toImmutableList(),
|
||||
numberOfMembers = space.spaceRoom.numJoinedMembers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CurrentSpace.Space -> item {
|
||||
SpaceHeaderView(
|
||||
avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader),
|
||||
name = space.spaceRoom.displayName,
|
||||
topic = space.spaceRoom.topic,
|
||||
visibility = space.spaceRoom.visibility,
|
||||
heroes = space.spaceRoom.heroes.toImmutableList(),
|
||||
numberOfMembers = space.spaceRoom.numJoinedMembers,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
}
|
||||
itemsIndexed(
|
||||
items = state.spaceRooms,
|
||||
key = { _, spaceRoom -> spaceRoom.roomId }
|
||||
) { index, spaceRoom ->
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onSpaceClick(spaceRoom.roomId)
|
||||
},
|
||||
onLongClick = {
|
||||
// TODO
|
||||
},
|
||||
)
|
||||
if (index != state.spaceRooms.lastIndex) {
|
||||
|
||||
item {
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = state.spaceRooms,
|
||||
key = { _, spaceRoom -> spaceRoom.roomId }
|
||||
) { index, spaceRoom ->
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onSpaceClick(spaceRoom.roomId)
|
||||
},
|
||||
onLongClick = {
|
||||
// TODO
|
||||
},
|
||||
)
|
||||
if (index != state.spaceRooms.lastIndex) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptySpaceHomeView(
|
||||
onCreateSpaceClick: () -> Unit,
|
||||
onExploreClick: () -> Unit,
|
||||
canExploreSpaces: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 32.dp, bottom = 16.dp, start = 40.dp, end = 40.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
BigIcon(
|
||||
style = BigIcon.Style.Default(CompoundIcons.SpaceSolid())
|
||||
)
|
||||
Text(
|
||||
text = stringResource(CommonStrings.screen_space_list_empty_state_title),
|
||||
style = ElementTheme.typography.fontHeadingLgBold,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
ButtonColumnMolecule {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(CommonStrings.action_create_space),
|
||||
onClick = onCreateSpaceClick,
|
||||
)
|
||||
if (canExploreSpaces) {
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(CommonStrings.action_explore_public_spaces),
|
||||
onClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeSpacesViewPreview(
|
||||
@@ -91,6 +171,7 @@ internal fun HomeSpacesViewPreview(
|
||||
state = state,
|
||||
lazyListState = rememberLazyListState(),
|
||||
onSpaceClick = {},
|
||||
modifier = Modifier,
|
||||
onCreateSpaceClick = {},
|
||||
onExploreClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ class HomePresenterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - NavigationBar is hidden when the last space is left`() = runTest {
|
||||
fun `present - NavigationBar is hidden when the last space is left when the user can't create new spaces`() = runTest {
|
||||
val homeSpacesPresenter = MutablePresenter(aHomeSpacesState())
|
||||
val presenter = createHomePresenter(
|
||||
sessionStore = InMemorySessionStore(
|
||||
@@ -193,7 +193,7 @@ class HomePresenterTest {
|
||||
val spaceState = awaitItem()
|
||||
assertThat(spaceState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
|
||||
// The last space is left
|
||||
homeSpacesPresenter.updateState(aHomeSpacesState(spaceRooms = emptyList()))
|
||||
homeSpacesPresenter.updateState(aHomeSpacesState(spaceRooms = emptyList(), canCreateSpaces = false))
|
||||
skipItems(1)
|
||||
val finalState = awaitItem()
|
||||
// We are back to Chats
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<string name="action_enable">"Enable"</string>
|
||||
<string name="action_end_poll">"End poll"</string>
|
||||
<string name="action_enter_pin">"Enter PIN"</string>
|
||||
<string name="action_explore_public_spaces">"Explore public spaces"</string>
|
||||
<string name="action_finish">"Finish"</string>
|
||||
<string name="action_forgot_password">"Forgot password?"</string>
|
||||
<string name="action_forward">"Forward"</string>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user