Browse Source

Merge pull request #2342 from element-hq/feature/jme/2330-add-empty-state-for-room-list

Add an empty state to the room list
pull/2343/head
Jorge Martin Espinosa 8 months ago committed by GitHub
parent
commit
aabca49c20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      changelog.d/2330.feature
  2. 6
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  3. 3
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
  4. 6
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
  5. 126
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt
  6. 16
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt
  7. 44
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt
  8. 20
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  9. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png
  10. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png
  11. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png
  12. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png

1
changelog.d/2330.feature

@ -0,0 +1 @@
Add empty state to the room list.

6
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -32,6 +33,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.api.NetworkStatus
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.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
@ -68,7 +70,9 @@ class RoomListPresenter @Inject constructor(
val matrixUser: MutableState<MatrixUser?> = rememberSaveable { val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
mutableStateOf(null) mutableStateOf(null)
} }
val roomList by roomListDataSource.allRooms.collectAsState() val roomList by produceState(initialValue = AsyncData.Loading()) {
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
}
val filteredRoomList by roomListDataSource.filteredRooms.collectAsState() val filteredRoomList by roomListDataSource.filteredRooms.collectAsState()
val filter by roomListDataSource.filter.collectAsState() val filter by roomListDataSource.filter.collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState()

3
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt

@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -28,7 +29,7 @@ import kotlinx.collections.immutable.ImmutableList
data class RoomListState( data class RoomListState(
val matrixUser: MatrixUser?, val matrixUser: MatrixUser?,
val showAvatarIndicator: Boolean, val showAvatarIndicator: Boolean,
val roomList: ImmutableList<RoomListRoomSummary>, val roomList: AsyncData<ImmutableList<RoomListRoomSummary>>,
val filter: String?, val filter: String?,
val filteredRoomList: ImmutableList<RoomListRoomSummary>, val filteredRoomList: ImmutableList<RoomListRoomSummary>,
val displayVerificationPrompt: Boolean, val displayVerificationPrompt: Boolean,

6
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt

@ -18,8 +18,10 @@ package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.aLeaveRoomState import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
@ -49,13 +51,15 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
) )
), ),
aRoomListState().copy(displayRecoveryKeyPrompt = true), aRoomListState().copy(displayRecoveryKeyPrompt = true),
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
) )
} }
internal fun aRoomListState() = RoomListState( internal fun aRoomListState() = RoomListState(
matrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"), matrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
showAvatarIndicator = false, showAvatarIndicator = false,
roomList = aRoomListRoomSummaryList(), roomList = AsyncData.Success(aRoomListRoomSummaryList()),
filter = "filter", filter = "filter",
filteredRoomList = aRoomListRoomSummaryList(), filteredRoomList = aRoomListRoomSummaryList(),
hasNetworkConnection = true, hasNetworkConnection = true,

126
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt

@ -17,7 +17,10 @@
package io.element.android.features.roomlist.impl package io.element.android.features.roomlist.impl
import androidx.compose.foundation.background import androidx.compose.foundation.background
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.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -35,6 +38,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -42,6 +46,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.leaveroom.api.LeaveRoomView import io.element.android.features.leaveroom.api.LeaveRoomView
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
import io.element.android.features.roomlist.impl.components.ConfirmRecoveryKeyBanner import io.element.android.features.roomlist.impl.components.ConfirmRecoveryKeyBanner
@ -51,17 +56,22 @@ import io.element.android.features.roomlist.impl.components.RoomListTopBar
import io.element.android.features.roomlist.impl.components.RoomSummaryRow import io.element.android.features.roomlist.impl.components.RoomSummaryRow
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
import io.element.android.libraries.architecture.AsyncData
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.Button
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
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.IconSource
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RoomListView( fun RoomListView(
@ -122,6 +132,35 @@ fun RoomListView(
} }
} }
@Composable
private fun EmptyRoomListView(
onCreateRoomClicked: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.screen_roomlist_empty_title),
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textSecondary,
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.screen_roomlist_empty_message),
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textSecondary,
)
Spacer(modifier = Modifier.height(16.dp))
Button(
text = stringResource(CommonStrings.action_start_chat),
leadingIcon = IconSource.Resource(CommonDrawables.ic_new_message),
onClick = onCreateRoomClicked,
)
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun RoomListContent( private fun RoomListContent(
@ -182,56 +221,59 @@ private fun RoomListContent(
) )
}, },
content = { padding -> content = { padding ->
LazyColumn( if (state.roomList is AsyncData.Success && state.roomList.data.isEmpty()) {
modifier = Modifier EmptyRoomListView(onCreateRoomClicked)
.padding(padding) } else {
.consumeWindowInsets(padding) LazyColumn(
.nestedScroll(nestedScrollConnection), modifier = Modifier
state = lazyListState, .padding(padding)
) { .consumeWindowInsets(padding)
when { .nestedScroll(nestedScrollConnection),
state.displayVerificationPrompt -> { state = lazyListState,
item { // FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
RequestVerificationHeader( contentPadding = PaddingValues(bottom = 80.dp)
onVerifyClicked = onVerifyClicked, ) {
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) } when {
) state.displayVerificationPrompt -> {
item {
RequestVerificationHeader(
onVerifyClicked = onVerifyClicked,
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) }
)
}
} }
} state.displayRecoveryKeyPrompt -> {
state.displayRecoveryKeyPrompt -> { item {
item { ConfirmRecoveryKeyBanner(
ConfirmRecoveryKeyBanner( onContinueClicked = onOpenSettings,
onContinueClicked = onOpenSettings, onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) } )
) }
} }
} }
}
if (state.invitesState != InvitesState.NoInvites) { if (state.invitesState != InvitesState.NoInvites) {
item { item {
InvitesEntryPointView(onInvitesClicked, state.invitesState) InvitesEntryPointView(onInvitesClicked, state.invitesState)
}
} }
}
itemsIndexed( val roomList = state.roomList.dataOrNull().orEmpty()
items = state.roomList, itemsIndexed(
contentType = { _, room -> room.contentType() }, items = roomList,
) { index, room -> contentType = { _, room -> room.contentType() },
RoomSummaryRow( key = { _, room -> room.roomId.value }
room = room, ) { index, room ->
onClick = ::onRoomClicked, RoomSummaryRow(
onLongClick = onRoomLongClicked, room = room,
) onClick = ::onRoomClicked,
if (index != state.roomList.lastIndex) { onLongClick = onRoomLongClicked,
HorizontalDivider() )
if (index != roomList.lastIndex) {
HorizontalDivider()
}
} }
} }
// Add a last Spacer item to ensure that the FAB does not hide the last room item
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
item {
Spacer(modifier = Modifier.height(80.dp))
}
} }
}, },
floatingActionButton = { floatingActionButton = {

16
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt

@ -21,6 +21,7 @@ import io.element.android.libraries.androidutils.diff.DiffCacheUpdater
import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.androidutils.diff.MutableListDiffCache
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -28,7 +29,9 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
@ -52,7 +55,7 @@ class RoomListDataSource @Inject constructor(
} }
private val _filter = MutableStateFlow("") private val _filter = MutableStateFlow("")
private val _allRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf()) private val _allRooms = MutableSharedFlow<ImmutableList<RoomListRoomSummary>>(replay = 1)
private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf()) private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
private val lock = Mutex() private val lock = Mutex()
@ -90,7 +93,7 @@ class RoomListDataSource @Inject constructor(
} }
val filter: StateFlow<String> = _filter val filter: StateFlow<String> = _filter
val allRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _allRooms val allRooms: SharedFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
@ -111,10 +114,9 @@ class RoomListDataSource @Inject constructor(
} }
private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>) { private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>) {
if (diffCache.isEmpty()) { if (diffCache.isEmpty() && roomListService.allRooms.loadingState.value is RoomList.LoadingState.NotLoaded) {
_allRooms.emit( // If the room list is not loaded, we emit a fake placeholders list
roomListRoomSummaryFactory.createFakeList() _allRooms.emit(RoomListRoomSummaryFactory.createFakeList())
)
} else { } else {
val roomListRoomSummaries = ArrayList<RoomListRoomSummary>() val roomListRoomSummaries = ArrayList<RoomListRoomSummary>()
for (index in diffCache.indices()) { for (index in diffCache.indices()) {
@ -133,7 +135,7 @@ class RoomListDataSource @Inject constructor(
private fun buildAndCacheItem(roomSummaries: List<RoomSummary>, index: Int): RoomListRoomSummary? { private fun buildAndCacheItem(roomSummaries: List<RoomSummary>, index: Int): RoomListRoomSummary? {
val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) { val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) {
is RoomSummary.Empty -> roomListRoomSummaryFactory.createPlaceholder(roomSummary.identifier) is RoomSummary.Empty -> RoomListRoomSummaryFactory.createPlaceholder(roomSummary.identifier)
is RoomSummary.Filled -> roomListRoomSummaryFactory.create(roomSummary) is RoomSummary.Filled -> roomListRoomSummaryFactory.create(roomSummary)
null -> null null -> null
} }

44
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt

@ -32,28 +32,30 @@ class RoomListRoomSummaryFactory @Inject constructor(
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter,
) { ) {
fun createPlaceholder(id: String): RoomListRoomSummary { companion object {
return RoomListRoomSummary( fun createPlaceholder(id: String): RoomListRoomSummary {
id = id, return RoomListRoomSummary(
roomId = RoomId("!aRoom:domain"), id = id,
isPlaceholder = true, roomId = RoomId(id),
name = "Short name", isPlaceholder = true,
timestamp = "hh:mm", name = "Short name",
lastMessage = "Last message for placeholder", timestamp = "hh:mm",
avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem), lastMessage = "Last message for placeholder",
numberOfUnreadMessages = 0, avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem),
numberOfUnreadMentions = 0, numberOfUnreadMessages = 0,
numberOfUnreadNotifications = 0, numberOfUnreadMentions = 0,
userDefinedNotificationMode = null, numberOfUnreadNotifications = 0,
hasRoomCall = false, userDefinedNotificationMode = null,
isDm = false, hasRoomCall = false,
) isDm = false,
} )
}
fun createFakeList(): ImmutableList<RoomListRoomSummary> { fun createFakeList(): ImmutableList<RoomListRoomSummary> {
return List(16) { return List(16) {
createPlaceholder("!fakeRoom$it:domain") createPlaceholder("!fakeRoom$it:domain")
}.toImmutableList() }.toImmutableList()
}
} }
fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary { fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {

20
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt

@ -166,14 +166,16 @@ class RoomListPresenterTests {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = consumeItemsUntilPredicate { state -> state.roomList.size == 16 }.last() val initialState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 16 }.last()
// Room list is loaded with 16 placeholders // Room list is loaded with 16 placeholders
assertThat(initialState.roomList.size).isEqualTo(16) val initialItems = initialState.roomList.dataOrNull().orEmpty()
assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue() assertThat(initialItems.size).isEqualTo(16)
assertThat(initialItems.all { it.isPlaceholder }).isTrue()
roomListService.postAllRooms(listOf(aRoomSummaryFilled())) roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last() val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 1 }.last()
assertThat(withRoomState.roomList.size).isEqualTo(1) val withRoomStateItems = withRoomState.roomList.dataOrNull().orEmpty()
assertThat(withRoomState.roomList.first()) assertThat(withRoomStateItems.size).isEqualTo(1)
assertThat(withRoomStateItems.first())
.isEqualTo(aRoomListRoomSummary) .isEqualTo(aRoomListRoomSummary)
scope.cancel() scope.cancel()
} }
@ -194,7 +196,7 @@ class RoomListPresenterTests {
skipItems(3) skipItems(3)
val loadedState = awaitItem() val loadedState = awaitItem()
// Test filtering with result // Test filtering with result
assertThat(loadedState.roomList.size).isEqualTo(1) assertThat(loadedState.roomList.dataOrNull().orEmpty().size).isEqualTo(1)
loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3)))
skipItems(1) skipItems(1)
val withFilteredRoomState = awaitItem() val withFilteredRoomState = awaitItem()
@ -384,10 +386,10 @@ class RoomListPresenterTests {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode) notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode)
val updatedState = consumeItemsUntilPredicate { state -> val updatedState = consumeItemsUntilPredicate { state ->
state.roomList.any { it.id == A_ROOM_ID.value && it.userDefinedNotificationMode == userDefinedMode } state.roomList.dataOrNull().orEmpty().any { it.id == A_ROOM_ID.value && it.userDefinedNotificationMode == userDefinedMode }
}.last() }.last()
val room = updatedState.roomList.find { it.id == A_ROOM_ID.value } val room = updatedState.roomList.dataOrNull()?.find { it.id == A_ROOM_ID.value }
assertThat(room?.userDefinedNotificationMode).isEqualTo(userDefinedMode) assertThat(room?.userDefinedNotificationMode).isEqualTo(userDefinedMode)
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
scope.cancel() scope.cancel()

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save