RoomList: refactor and fix tests
This commit is contained in:
parent
a2c4d7debd
commit
bf68261ed9
20 changed files with 252 additions and 107 deletions
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
open class RoomListContentStateProvider : PreviewParameterProvider<RoomListContentState> {
|
||||
override val values: Sequence<RoomListContentState>
|
||||
get() = sequenceOf(
|
||||
aRoomsContentState(),
|
||||
aSkeletonContentState(),
|
||||
anEmptyContentState(),
|
||||
aMigrationContentState(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRoomsContentState(
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||
) = RoomListContentState.Rooms(
|
||||
invitesState = invitesState,
|
||||
securityBannerState = securityBannerState,
|
||||
summaries = summaries,
|
||||
)
|
||||
|
||||
internal fun aMigrationContentState() = RoomListContentState.Migration
|
||||
|
||||
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
||||
|
||||
internal fun anEmptyContentState(
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
) = RoomListContentState.Empty(invitesState)
|
||||
|
|
@ -40,7 +40,7 @@ import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
|||
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.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
|
@ -89,7 +89,7 @@ class RoomListPresenter @Inject constructor(
|
|||
private val indicatorService: IndicatorService,
|
||||
private val filtersPresenter: Presenter<RoomListFiltersState>,
|
||||
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
private val migrationScreenPresenter: Presenter<MigrationScreenState>,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomListState> {
|
||||
|
|
@ -196,23 +196,23 @@ class RoomListPresenter @Inject constructor(
|
|||
}
|
||||
val loadingState by roomListDataSource.loadingState.collectAsState()
|
||||
val showMigration = migrationScreenPresenter.present().isMigrating
|
||||
val showSkeleton by remember {
|
||||
derivedStateOf {
|
||||
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
||||
}
|
||||
}
|
||||
val showEmpty by remember {
|
||||
derivedStateOf {
|
||||
(loadingState as? RoomList.LoadingState.Loaded)?.numberOfRooms == 0
|
||||
}
|
||||
}
|
||||
val showSkeleton by remember {
|
||||
derivedStateOf {
|
||||
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
||||
}
|
||||
}
|
||||
return when {
|
||||
showMigration -> RoomListContentState.Migration
|
||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||
showEmpty -> {
|
||||
val invitesState = inviteStateDataSource.inviteState()
|
||||
RoomListContentState.Empty(invitesState)
|
||||
}
|
||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||
else -> {
|
||||
val invitesState = inviteStateDataSource.inviteState()
|
||||
val securityBannerState by securityBannerState(securityBannerDismissed)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ enum class SecurityBannerState {
|
|||
RecoveryKeyConfirmation,
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomListContentState {
|
||||
data object Migration : RoomListContentState
|
||||
data class Skeleton(val count: Int) : RoomListContentState
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ 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.search.RoomListSearchState
|
||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
|
|
@ -35,7 +34,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
|||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
override val values: Sequence<RoomListState>
|
||||
|
|
@ -43,15 +41,15 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
|||
aRoomListState(),
|
||||
aRoomListState(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
||||
aRoomListState(hasNetworkConnection = false),
|
||||
aRoomListState(invitesState = InvitesState.SeenInvites),
|
||||
aRoomListState(invitesState = InvitesState.NewInvites),
|
||||
aRoomListState(contentState = aRoomsContentState(invitesState = InvitesState.SeenInvites)),
|
||||
aRoomListState(contentState = aRoomsContentState(invitesState = InvitesState.NewInvites)),
|
||||
aRoomListState(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||
aRoomListState(contextMenu = aContextMenuShown(isFavorite = true)),
|
||||
aRoomListState(securityBannerState = SecurityBannerState.SessionVerification),
|
||||
aRoomListState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||
aRoomListState(roomList = AsyncData.Success(persistentListOf())),
|
||||
//aRoomListState(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||
aRoomListState(matrixUser = null, displayMigrationStatus = true),
|
||||
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification)),
|
||||
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation)),
|
||||
aRoomListState(contentState = anEmptyContentState()),
|
||||
aRoomListState(contentState = aSkeletonContentState()),
|
||||
aRoomListState(matrixUser = null, contentState = aMigrationContentState()),
|
||||
aRoomListState(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")),
|
||||
aRoomListState(filtersState = aRoomListFiltersState(isFeatureEnabled = true)),
|
||||
)
|
||||
|
|
@ -60,16 +58,13 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
|||
internal fun aRoomListState(
|
||||
matrixUser: MatrixUser? = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||
showAvatarIndicator: Boolean = false,
|
||||
roomList: AsyncData<ImmutableList<RoomListRoomSummary>> = AsyncData.Success(aRoomListRoomSummaryList()),
|
||||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
invitesState: InvitesState = InvitesState.NoInvites,
|
||||
contextMenu: RoomListState.ContextMenu = RoomListState.ContextMenu.Hidden,
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
searchState: RoomListSearchState = aRoomListSearchState(),
|
||||
filtersState: RoomListFiltersState = aRoomListFiltersState(isFeatureEnabled = false),
|
||||
displayMigrationStatus: Boolean = false,
|
||||
contentState: RoomListContentState = aRoomsContentState(),
|
||||
eventSink: (RoomListEvents) -> Unit = {}
|
||||
) = RoomListState(
|
||||
matrixUser = matrixUser,
|
||||
|
|
@ -80,11 +75,7 @@ internal fun aRoomListState(
|
|||
leaveRoomState = leaveRoomState,
|
||||
filtersState = filtersState,
|
||||
searchState = searchState,
|
||||
contentState = RoomListContentState.Rooms(
|
||||
invitesState = invitesState,
|
||||
securityBannerState = securityBannerState,
|
||||
summaries = roomList.dataOrNull().orEmpty().toPersistentList(),
|
||||
),
|
||||
contentState = contentState,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
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
|
||||
|
|
@ -40,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
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.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
|
@ -48,19 +48,24 @@ import io.element.android.features.roomlist.impl.InvitesEntryPointView
|
|||
import io.element.android.features.roomlist.impl.InvitesState
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.RoomListContentState
|
||||
import io.element.android.features.roomlist.impl.RoomListContentStateProvider
|
||||
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.filters.aRoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
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.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun RoomListContentView(
|
||||
|
|
@ -214,7 +219,6 @@ private fun RoomsViewList(
|
|||
modifier = modifier.nestedScroll(nestedScrollConnection),
|
||||
// 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 -> {
|
||||
|
|
@ -261,7 +265,7 @@ private fun RoomsViewList(
|
|||
|
||||
@Composable
|
||||
private fun EmptyViewForFilterStates(
|
||||
selectedFilters: List<RoomListFilter>,
|
||||
selectedFilters: ImmutableList<RoomListFilter>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) ?: return
|
||||
|
|
@ -301,3 +305,18 @@ private fun EmptyScaffold(
|
|||
action?.invoke(this)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStateProvider::class) state: RoomListContentState) = ElementPreview {
|
||||
RoomListContentView(
|
||||
contentState = state,
|
||||
filtersState = aRoomListFiltersState(),
|
||||
eventSink = {},
|
||||
onVerifyClicked = { },
|
||||
onConfirmRecoveryKeyClicked = { },
|
||||
onRoomClicked = {},
|
||||
onRoomLongClicked = {},
|
||||
onCreateRoomClicked = { },
|
||||
onInvitesClicked = { })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,9 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomListRoomSummaryFactory @Inject constructor(
|
||||
|
|
@ -52,7 +50,6 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
|||
isFavorite = false,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
|
@ -34,4 +36,7 @@ interface RoomListModule {
|
|||
|
||||
@Binds
|
||||
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
||||
|
||||
@Binds
|
||||
fun bindMigrationScreenPresenter(presenter: MigrationScreenPresenter): Presenter<MigrationScreenState>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ data class RoomListFiltersEmptyStateResources(
|
|||
@StringRes val title: Int,
|
||||
@StringRes val subtitle: Int,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a [RoomListFiltersEmptyStateResources] from a list of selected filters.
|
||||
|
|
|
|||
|
|
@ -34,14 +34,11 @@ class RoomListFiltersPresenter @Inject constructor(
|
|||
private val featureFlagService: FeatureFlagService,
|
||||
private val filterSelectionStrategy: FilterSelectionStrategy,
|
||||
) : Presenter<RoomListFiltersState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomListFiltersState {
|
||||
|
||||
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false)
|
||||
val filters by filterSelectionStrategy.filterSelectionStates.collectAsState()
|
||||
|
||||
|
||||
fun handleEvents(event: RoomListFiltersEvents) {
|
||||
when (event) {
|
||||
RoomListFiltersEvents.ClearSelectedFilters -> {
|
||||
|
|
@ -75,7 +72,6 @@ class RoomListFiltersPresenter @Inject constructor(
|
|||
roomListService.allRooms.updateFilter(allRoomsFilter)
|
||||
}
|
||||
|
||||
|
||||
return RoomListFiltersState(
|
||||
filterSelectionStates = filters.toPersistentList(),
|
||||
isFeatureEnabled = isFeatureEnabled,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl.filters
|
|||
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
data class RoomListFiltersState(
|
||||
val filterSelectionStates: ImmutableList<FilterSelectionState>,
|
||||
|
|
@ -26,7 +27,10 @@ data class RoomListFiltersState(
|
|||
) {
|
||||
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }
|
||||
|
||||
fun selectedFilters(): List<RoomListFilter> {
|
||||
return filterSelectionStates.filter { it.isSelected }.map { it.filter }
|
||||
fun selectedFilters(): ImmutableList<RoomListFilter> {
|
||||
return filterSelectionStates
|
||||
.filter { it.isSelected }
|
||||
.map { it.filter }
|
||||
.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ fun RoomListFiltersView(
|
|||
state: RoomListFiltersState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
||||
fun onClearFiltersClicked() {
|
||||
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import javax.inject.Inject
|
|||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultFilterSelectionStrategy @Inject constructor() : FilterSelectionStrategy {
|
||||
|
||||
private val selectedFilters = LinkedHashSet<RoomListFilter>()
|
||||
|
||||
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface FilterSelectionStrategy {
|
||||
|
||||
val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
|
||||
|
||||
fun select(filter: RoomListFilter)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue